くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログ。積極採用中です。

TypeScript の特徴を他の言語と比較してみた

こんにちは。2020年4月に新卒で入社した Katayama です。

私は学生時代、主に C / C++Python を書いていました。TypeScript は くらしのマーケットのバックエンドで使われており、入社して初めて触った言語です。 これまで触った言語のとの違いや、特徴的な型推論について調べてみました。

この内容は 9/18 に宮崎で開催された Webナイト宮崎 Vol.10 ~てげTypeScriptを学びたい~ で「TypeScript を初めて触った感想と型推論について」というタイトルで発表したものを記事として再編成・加筆したものになります。

言語の比較

学生時代触っていた C / C++Python を TypeScript と比較しました。主観的な部分が含まれていますが、ご容赦ください。

実行速度の比較

Python

Python の標準の実装である CPyhon はインタプリタ言語であり、動的型付けを採用したことでコードを評価するたびに型のチェックが必要になることから、メモリアクセスの効率が悪いです。更に GIL の制約によりマルチスレッドのパフォーマンスも良くありません。しかし、PyPy のような JIT コンパイルを採用している処理系であれば CPython の3倍程度高速に実行することができるようです。

C / C++

最終的には機械語が出力されるため高速に実行できます。 学生時代作成したリバーシの思考ルーチンを Python から C++ に書き直した所約140倍速くなった経験があり、高速な言語といえばこれ、というイメージが強いです。

TypeScript

JavaScriptコンパイルして実行する必要があるので、JavaScript の実行速度になります。 Node.js では JIT Virtual Machine 型の V8 が使われており、高速に実行できます。

型についての比較

Python

整数や文字列、関数を含めたすべてのデータがオブジェクト(object)です。式の評価時に型をチェックする動的型付け言語です。

C / C++

char(8bit整数)、int(32bit整数 1)、float(32bit浮動小数点)と比較的低レベルな静的型付け言語です。型が違っていても暗黙的にキャストされるため、例えば以下のように意図しない動作をしてしまうことがあります。(重要じゃないコードは省略しています) また、高級言語ではあるものの、ポインタ や malloc関数 などの低レベルな処理を書くことができる反面、メモリ安全性を考慮する必要があり注意することが多いです。

char c = 'A'
int a = c;  // 65

double f = 3.14;
int b = f; // 3

int i = 100000;
short s = i;  // -31072

TypeScript

静的型付け、動的型付けのどちらも使うことができます。また、プリミティブ型は以下の種類しかなく、C言語と比べるとシンプルです。整数と小数を区別されておらず、どちらも number型 です 2。また、後述の型推論を使うことで、最低限のアノテーションで型安全なプログラムを記述できます。

  • number
  • bigint
  • string
  • boolean
  • null
  • undefined
  • symbol

パフォーマンスの比較

個人的には実行速度が気になりましたので、以下のような再帰関数で35番目のフィボナッチ数( = 9,227,465 )を求めるプログラムで実行時間を計測してみました。 シンプルなプログラムですが、計算量が文字通り指数的に増大します。複雑な計算がないので計算のコストよりも関数呼び出しのコストが大きく出るかもしれません。

# Python
def f(n):
    if n <= 1:
        return n
    else:
        return f(n - 2) + f(n - 1)

y = f(35)
言語 (実行条件) 実行時間
C 言語 ( GCC -O3 ) 0.027 s
C 言語 ( GCC -O2 ) 0.037 s
C 言語 ( GCC ) 0.074 s
JavaScript (Node.js) 0.097 s
Python ( PyPy ) 0.35 s
Python ( CPython ) 3.5 s

実行環境
OS: Ubuntu 18.04
CPU: AMD Ryzen 5 2600
RAM: 8GB 2667Hz 2枚

実行環境や使用したソフトウェアのバージョンによって結果が変わるので参考程度の実測値です。 JavaScriptGCCコンパイルした C言語には敵いませんが、PyPy で実行した Python よりも約30倍速かったです。

型推論について

TypeScript で特徴的だと感じたのが型推論の機能です。C言語では必ず型を宣言する必要があり、型が違っていても暗黙的にキャストされるため代入できる場合があります 3。TypeScript では型を指定せずとも 3 を number 型と推論されます。それによって一貫性を守るように強制されるので型によるバグが減ります。

// C言語
char s[] = "ABC";
int i = 3;
i = s;  // -647713340 !?
// TypeScript
const s: string = "ABC";
let i = 3
i = s  // ERROR !!

次に、どのような記述なら正しく推論されるのかを調べてみました。プリミティブ型や、プリミティブ型で初期化したオブジェクトは問題なく型推推論できました。 ただ、宣言時に初期化をしない場合では any型 になってしまいます。初期化無しで変数を宣言するにはアノテーションが必要です。

// TypeScript
const i = 15;        // number
const I = 9007199254740991n; // bigint
const s = “hello”;   // string
const b = true;      // boolean
const n = null;      // null
const u = undefined; // undefined
const sym = Symbol("foo");  // symbol

// { name: string; sales: { jpy: number; usd: number; }[] }
const complex = {
  name: "taro",
  sales: [
      {jpy: 1200, usd: 12},
      {jpy: 810, usd: 8.1},
  ],
};

// 宣言をしたあとに代入
let x;
x = 3;  // any

let y: number;
y = 3;  // number

関数(の返り値)でも型推論は機能しますが、number型同士でのみ許可される * 演算子を戻り値にしたような場合だけです。関数の場合はアノテーションした方が良さそうです。

// TypeScript

// 返り値が推論できる
function mul(a, b){
    return a * b;
}
const y1 = mul(7, 5);  // number

// 返り値が推論できない
function add(a, b){
    return a + b;
}
const y2 = add(7, 5);  // any

既存の JavaScript のライブラリで型安全の恩恵を受ける

アンビエント宣言を記述することで、JavaScript で書かれたライブラリを TypeScript から呼び出す際に型安全の恩恵を受けることができるようになります。例えば以下はコミュニティが管理している Node.js の console.log のアンビエント宣言の抜粋です。(関係のない部分は省略してます)

interface Console {
  Console: NodeJS.ConsoleConstructor;
  log(message?: any, ...optionalParams: any[]): void;
}
<200b>
declare var console: Console;

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v8/base.d.ts

まとめ

TypeScript は適切に書くことで、効率よく型安全なプログラムを作ることとができ、パフォーマンスも優れた言語だと感じました。しかし、基本である JavaScript の仕様を完全に把握できておらず、業務で混乱することがありましたので、まだまだ勉強が必要そうです!!


  1. int型のbit数はC言語の規格として定義されていないため、CPUによって64bitや、マイコンでは8bit、16bitが使われることがあります。

  2. 64bitの浮動小数点が使われていますが、整数を格納すると53bit精度になるようです。より大きな整数はbigint型で宣言します。

  3. char*(ポインタ)からintへ暗黙的にキャストされたため、アドレスが代入されています。また、実行ごとにこの値は変化します。

Web ナイト宮崎 Vol.10 ~てげ TypeScript を学びたい~ で登壇しました

f:id:curama-tech:20201009174601p:plain

こんにちは、バックエンドエンジニアの @akira です。

9 月 18 日に宮崎で開催された勉強会【Web ナイト宮崎 Vol.10 ~てげ TypeScript を学びたい~】で「TypeScript の列挙型について」というテーマで発表してきました!

今回はその内容を記事として再編成・加筆したものになります。


はじめに

今回は 「TypeScript Enum 〜使いどころの難しい Enum ??〜」 というテーマで発表させて頂きます。

発表内容は以下の二点です。

  • TypeScript Enum の基礎
  • 複雑な Enum の表現方法

Agenda は次のようになっています。

  • Numeric Enum
  • Union of Literal Type
  • String Enum
  • Const Enum
  • Namespace
  • Enum Class
  • 制約まとめ

この後提示するコードは全て TypeScript Playground v4.0.2 にて確認しています。

それでは早速 Numeric Enum から見ていきましょう。

Numeric Enum

Numeric Enum とは number 型の値をもつ Enum です。

enum Fruits {
  Apple,
  Orange,
  Lemon,
}

let fruit = Fruits.Apple;
console.log(fruit); // 0
fruit = 1; // number is also acceptable
fruit = 4; // not error

number 型の値なら代入できてしまうため、型安全ではありません。
Enum で指定した値以外の number も代入が可能です。

string 型は代入できません。

fruit = "Apple"; // Error: Type '"Apple"' is not assignable to type 'Fruits'.

トランスパイルされた JavaScript を見てみます。

// JS
var Fruits;
(function (Fruits) {
  Fruits[(Fruits["Apple"] = 0)] = "Apple";
  Fruits[(Fruits["Orange"] = 1)] = "Orange";
  Fruits[(Fruits["Lemon"] = 2)] = "Lemon";
})(Fruits || (Fruits = {}));

Fruits["Apple"] = 0 は 0 を返し(reverse mapping)、Value と Key のどちらも指定が可能です。
範囲外の値を指定した場合は undefined が返されます。

console.log(Fruits[0]); // "Apple"
console.log(Fruits["Apple"]); // 0
console.log(Fruits.Apple); // 0
console.log(Fruits[10]); // undefined

また、値の初期値は明示的に指定可能です。

enum Fruits {
  Apple = 1,
  Orange, // 2
  Lemon, // 3
}

console.log(Fruits.Orange); // 2

Union of Literal Type

リテラル型の Union 型は指定した値以外は代入できないため、Numeric Enum に代わる良い選択肢となるでしょう。

const Fruit = {
  Apple: 0,
  Orange: 1,
  Lemon: 2,
} as const;
type Fruit = typeof Fruit[keyof typeof Fruit]; // 0 | 1 | 2

let f: Fruit;
f = 0; // OK
f = Fruit.Lemon; // OK
f = 4; // Error: Type '4' is not assignable to type 'Fruit'.

トランスパイルされた JavaScript はとてもシンプルです。

// JS
const Fruit = {
  Apple: 0,
  Orange: 1,
  Lemon: 2,
};

String Enum

続いて String Enum です。
String Enum は string 型の値をもつ Enum ですが、string 型自体は代入できないため、型安全です。

enum Fruits {
  Apple = "Apple",
  Orange = "Orange",
  Lemon = "Lemon",
}

let fruit = Fruits.Apple;
fruit = "Orange"; // Error: Type '"Orange"' is not assignable to type 'Fruits'

トランスパイルされた JavaScript を見てみます。

// JS
var Fruits;
(function (Fruits) {
  Fruits["Apple"] = "Apple";
  Fruits["Orange"] = "Orange";
  Fruits["Lemon"] = "Lemon";
})(Fruits || (Fruits = {}));

reverse mapping もされておらず、Key のみ指定可能です。

enum Fruits {
  Apple = "Apple",
  Orange = "Orange",
  Lemon = "Lemon",
}

console.log(Fruits["Apple"]); // "Apple"
console.log(Fruits.Apple); // "Apple"

Value を Key と同じにすることで、可読性が上がり、debug しやすいのではないでしょうか。

Const Enum

次は Const Enum です。

Const Enum は、Numeric Enumconst キーワードが付与された Enum です。

const enum Fruits {
  Apple,
  Orange,
  Lemon,
}

Const Enum の特徴を説明する前に、先程の Numeric Enum のトランスパイル後の JavaScript をもう一度見てみましょう。

enum Fruits {
  Apple,
  Orange,
  Lemon,
}

const apple = Fruits.Apple;
// JS
var Fruits;
(function (Fruits) {
  Fruits[(Fruits["Apple"] = 0)] = "Apple";
  Fruits[(Fruits["Orange"] = 1)] = "Orange";
  Fruits[(Fruits["Lemon"] = 2)] = "Lemon";
})(Fruits || (Fruits = {}));

const apple = Fruits.Apple;

上記を見ると、即時関数によってオブジェクトを変数に代入していることがわかります。

Const Enum を使った以下のコードの、トランスパイル後の JavaScript を見てみます。

const enum Fruits {
  Apple,
  Orange,
  Lemon,
}

const apple = Fruits.Apple;
// JS
const apple = 0; /* Apple */

Numeric Enum と比べ、値がインライン化されていることがわかります。よってパフォーマンスの向上が期待できます。

ただし、Const Enum も Numeric Enum 同様、型安全ではありません。

const enum Fruits {
  Apple,
  Orange,
  Lemon,
}

let fruit = Fruits.Apple;
fruit = 1; // number is also acceptable
fruit = 10; // not error

Namespace

Namespace を使うと、Enum にメソッドを追加することができます。

enum Fruits {
  Apple,
  Orange,
  Lemon,
}

namespace Fruits {
  export function isApple(fruit: Fruits): boolean {
    switch (fruit) {
      case Fruits.Apple:
        return true;
      default:
        return false;
    }
  }
}

ただし、Const Enum には Namespace を定義できません。

const enum Fruits { // Error: Enum declarations can only merge with namespace or other enum declarations.
  Apple,
  Orange,
  Lemon,
}

// Error: Enum declarations can only merge with namespace or other enum declarations.
namespace Fruits {
  export function isApple(fruit: Fruits): boolean {
    switch (fruit) {
      case Fruits.Apple:
        return true;
      default:
        return false;
    }
  }
}

Enum Class

ここまで一通り Enum を見てきましたが、Numeric Enum や Const Enum には制約があったかと思います。
メソッドを持つ少し複雑な Enum を表現したい場合、弊社では Enum 用の Class を定義しています。

まず Enum の抽象クラスを定義します。

abstract class Enum<T> {
  constructor(readonly value: T) {}

  public is(e: Enum<T>): boolean {
    return this.value === e.value;
  }

  public isNot(e: Enum<T>): boolean {
    return !this.is(e);
  }
}

続いて具象クラスを String Enum と共に定義します。

enum FruitNames {
  Apple = "Apple",
  Orange = "Orange",
}

class Fruits extends Enum<string> {
  public static [FruitNames.Apple]: Fruits = new Fruits(FruitNames.Apple, 100, 0.5);
  public static [FruitNames.Orange]: Fruits = new Fruits(FruitNames.Orange, 200, 1);

  constructor(value: string, readonly price: number, readonly weight: number) {
    super(value);
  }
}

定数は String Enum に、ロジックは Class に定義します。
また、Static Field として各オブジェクトを保持することで Enum のように扱えます。

上記の例では、りんごやオレンジなどの果物に対し、料金と重さの紐付けを表現しています。

クライアントコードは次のようになります。

let fruit = Fruits.Orange;
if (fruit.is(Fruits.Orange)) {
  console.log(fruit.price, fruit.weight);
}
fruit = "Orange"; // Error: Type 'string' is not assignable to type 'Fruits'.

上記のように、string 型の値は代入できないため、型安全です。

制約まとめ

それぞれ詳細を見てきましたが、制約をまとめると以下になります。

範囲外の値を代入できない 型安全
Numeric Enum
Union of Literal Type
String Enum
Const Enum
Enum Class

最後に

今回のまとめです。

  • Enum は万能ではない
  • Union of Literal Type も良い選択肢である
  • 複雑な Enum は Class を使うのがおすすめ

以上になります。ありがとうございました!

Ansible の variable register は when でスキップできない

f:id:curama-tech:20200930155627p:plain

こんにちは。 バックエンドエンジニア / SRE のまのめです。

くらしのマーケットのデプロイには、 Ansible が採用されています。
Ansible では、実行したコマンドの結果などを変数に入れる register というキーワードがあります。
小ネタですが、この register で本番デプロイ時にハマったので、そのことを書いていきます。

問題の Task

やりたかったことは、以下のような処理です。

もし env が prod なら本番環境用の 設定値 を、
そうではなく env が kaizen なら 改善環境用の 設定値 を
AWS の Parameter Store から取ってきて、register する

これを愚直に playbook に起こすと、以下のようになります。

- name: Set Prod Config
  command: aws ssm get-parameters --region ap-northeast-1 --name prod.config --query Parameters[0].Value --output text
  register: config
  when: "env == 'prod'"

- name: Set Kaizen Config
  command: aws ssm get-parameters --region ap-northeast-1 --name kaizen.config --query Parameters[0].Value --output text
  register: config
  when: "env == 'kaizen'"

- name: Print Result
  command: "echo '{{ config.stdout }}'"

※ Print している箇所は、実際は template に render するなどしています。

さて、結果はどうなるでしょうか。

  • 改善環境( env = 'kaizen' ): 改善環境用の設定値がセットされる
  • 本番環境( env = 'prod' ): dict object has no attribute stdout という エラー

本番デプロイ中だというのに、困りました。

原因

先程の例で、 env = 'prod' のとき、config に register されていたものは、以下のような dict です。

"msg": {
    "changed": false, 
    "skip_reason": "Conditional result was False", 
    "skipped": true
}

condition が false になるパターンは env == 'kaizen' の場合ですね。
register がスキップされたのではなく、「評価されて false になった」という内容が register されて上書きされてしまっていました。

つまり記事タイトルのとおりですが、Ansible の register は when によってスキップできないようです。
ドキュメントにも書いてありました。

If a task fails or is skipped, Ansible still registers a variable with a failure or skipped status, unless the task is skipped based on tags.

Using Variables / Ansible Documentation

回避策

この箇所をコメントアウトし、ファイルに直書きしたものを読み込む方式に切り替えました。
本当は、「値が更新されても 1 箇所変えれば全てに適用されるようにしたい」という意図で Parameter Store から取る方式にしていたので、ちょっと不本意な形です。
何かいい方法はないのでしょうか……もし知見がある方がいらっしゃいましたら、教えていただきたいです。

最後に

こういう小ネタも案外ドキュメントにちゃんと書いてあることもあるので、しっかりと目を通しておかないといけないですね。
なお、結局未だにいい解決方法が確立できていません。
くらしのマーケットは、まだまだデプロイ周りでもこういった改善ができるところがたくさんあります。
SRE として一緒に育ててくださる方は、ぜひ こちら からお気軽にご連絡ください!

TypeScriptを使ってDiscord botを作ってみよう!

はじめに

こんにちは!今年新卒入社しました、エンジニアのタナカです。

早速ですが、ここ最近リモートワークが推奨されている中、「Discord(ディスコード)」というツールを導入した、もしくは導入してはいないけれども単語は聞いたことがあるという方が増えているのではないでしょうか。

このツールは過去に 「ゲーマー向けチャットツール」 とよく言われてましたが、2020年7月にブランディングイメージが 「あらゆるコミュニティが使えるコミュニケーションツール」 に変更され、ゲームだけに留まらないツールとなりそうです。

音声も結構クリーンな感じなのもまたいいところですよね👍

さて、この「Discord」というツールなのですが…実はこれ、チャットや会話をするだけではなく、botだって作れるんです。

私も実際にbotを作ってみましたが、とても楽しく作れます!

「プログラミング一通り学んだけど次何したら良いか分かんない…」という方にもきっとオススメできます。

なので今回は、TypeScriptでのDiscord botの作り方(コード部分)について書いていきたいと思います!

※本記事は9/18(金)に協賛枠で参加したWebナイト宮崎 Vol.10 ~てげTypeScriptを学びたい~にて Tunakan という名義で発表した内容から一部を抜粋し、ブログ用に編集したものになります。

 

Discord bot 作ってみよう!

準備

事前に必要なものは以下になります。

…え?これだけ?

そうです。これだけで作れちゃいます!

24時間フル稼働させるような規模のものでなければ、たったの3つで出来ます!

ちなみに今回JavaScriptではなくTypeScriptを使う理由としては、くらしのマーケットで使われている言語の1つなので使い慣れているという点があります。

そして以下が作成したサーバーにbotを追加した状態になります。

f:id:curama-tech:20200924134126p:plain

右側に 「Tunakan bot というのがオフラインで存在してますね。こちらが今回準備したbotになります。

…おや?何やらDiscord画面の下の方に面白い言葉が書いてありますね?

f:id:curama-tech:20200924134221p:plain

「ご挨拶しな!」と半ば強引に挨拶を求められてますね…😅

ただこの状態だと(作成してすぐなので当然ですが)何も返してくれません。シカトされちゃいます。悲しいですね…

なので、まずは簡単に挨拶出来る機能を追加しちゃいましょう!💪

botが挨拶出来るようにしよう!

最初に、discord.jsというNode.jsモジュールをインストールします。MacLinux系ならターミナル、Windowsならコマンドプロンプト、その他お好きなコマンドラインツールがあればそちらを開き以下を実行しましょう!

npm install discord.js

次にテキストエディタを開いて以下のコードをサクッと書いて保存してください。

// ./bot.ts

import { Client, Message } from "discord.js";
import { config } from "./config";

const client = new Client();

interface ReplyInterface {
    messageReply(message: Message): Promise<void>;
}

class Reply implements ReplyInterface {
    public async messageReply(message: Message): Promise<void> {
        if (message.author.bot) {
            return;
        } else if (message.content === "こんにちは") {
            message.reply("こんにちは!");
        }
    }
}

const reply = new Reply();

client.on("message", (message) => reply.messageReply(message));

client.login(config.token);
// ./config.ts

export const config = {
    token: "{token}",   // Developer Portalからtokenをコピペする
};

ファイル構成は以下のようになるはずです。

f:id:curama-tech:20200924134244p:plain

このサクッと書いたコードですが、以下の流れになります

  1. client.loginbotがオンライン状態になる
  2. 何かしらのメッセージが送信されたらreply.messageReply(message)を実行
  3. if (message.author.bot) { return; }で、メッセージがbotからならreturn
  4. bot以外で"こんにちは"というメッセージが送信されたら"こんにちは!"と発言者に対してリプライをする

bot作るなら結構複雑なコード書かなきゃいけないんじゃ…?と思われそうですが(実際私もそう思っていました)、単純なものならこの通りたった数行で作れます!👍

では実際に動かしてみましょう!以下をコマンドラインツールで実行してみてください!

.tsファイルのコンパイルを行った後、botを起動します!

tsc bot.ts
node bot.js

f:id:curama-tech:20200924134306p:plain

f:id:curama-tech:20200924134320p:plain

うまく行けばこの通り、Tunakan botがオンライン状態になり、挨拶をしてくれます!

挨拶だけじゃ物足りない!もっと凝ったものを作る

さて、挨拶するだけじゃかなり物足りないですよね?

せっかく簡単に作れるんだから、何かもうちょっと凝ったものを作りたい…少し手軽にbotらしく動作させたい…うーん…

そうだ、じゃんけんゲームを作ろう!

というわけでじゃんけんのコードをサクッと書いちゃいましょう。

import { Collection } from "@discordjs/collection";
import { Client, Message } from "discord.js";
import { config } from "./config";

const client = new Client();

enum hands {
    "グー",
    "チョキ",
    "パー",
}

interface ReplyInterface {
    messageReply(message: Message): Promise<void>;
}

interface GameInterface {
    playRockPaperScissorsGame(message: Message): Promise<void>;
}

class Reply implements ReplyInterface {
    private game = new Game();

    public async messageReply(message: Message): Promise<void> {
        if (message.content === "じゃんけん") {
            await this.game.playRockPaperScissorsGame(message);
        }
    }
}

class Game implements GameInterface {
    public async playRockPaperScissorsGame(message: Message): Promise<void> {
        await message.reply("最初はグー!じゃんけん…!");
        const filter = (player) => {
            return ["グー", "チョキ", "パー"].includes(player.content);
        };

        let player: Collection<string, Message>;
        try {
            player = await message.channel.awaitMessages(filter, { max: 1, time: 10000, errors: ["time"] });
        } catch {
            message.reply("タイムオーバー!あなたの負けです");
            return;
        }

        if (!player) {
            // playerがundefinedならログアウトしてエラーを出す
            client.destroy();
            throw new Error("player is undefined");
        }

        // botの手を決める
        const botHand = Math.floor(Math.random() * 3);
        await message.reply(hands[botHand]);

        // 判定を行う
        const judge: number = (hands[player.first().content] - botHand + 3) % 3;
        switch (judge) {
            case 0:
                await message.reply("あいこ");
                break;
            case 1:
                await message.reply("あなたの負け");
                break;
            case 2:
                await message.reply("あなたの勝ち");
                break;
            default:
                await message.reply("Error!");
                break;
        }
    }
}

const reply = new Reply();
client.on("message", (message) => reply.messageReply(message));
client.login(config.token);

処理の流れは以下の通りです。サクッと作成したものなので、あいこでも再度勝負をしない一発勝負という点にご注意ください。

  1. ユーザーが「じゃんけん」とメッセージを送るとplayRockPaperScissorsGame()を実行
  2. 「最初はグー!じゃんけん…!」とreplyし、ユーザーからのメッセージをawaitMessagesで待つ
  3. filterで設定した文字列「グー」「チョキ」「パー」のメッセージが送信された時、bot側の手をランダムで決め、botの手をreplyする
  4. 計算してじゃんけんの判定を行い、それぞれの結果をreplyする

また、じゃんけんの判定ロジックは以下になります

  1. グー(0) チョキ(1) パー(2)とする
  2. ( {プレイヤーの手} - {botの手} + 3 ) % 3 をする
  3. 計算結果が0ならあいこ、1なら負け、2なら勝ちになる

コードが書けたらbotを起動してみましょう!tsc bot.tsコンパイルも忘れずに!

f:id:curama-tech:20200924134337p:plain

さいごに

Discordのbotは気軽に作れて、プログラミングの勉強にもなると思います!

Discordを使っている、サーバー持ってたりしててもうちょっといい感じにしたい!楽に運用したい!って時や、TypeScriptを勉強したい…!って時に作ってみるのもいいかもしれませんね👍

ログイン画面の「当たり前」を調査

みんなのマーケットでUI/UXデザイナーをしているミソサクです。

モバイルデザインの「当たり前」の変化はすごい早いです。その早さに置いていかれないように、今回は2020年9月現在の「ログイン画面」における「当たり前」を調査します。

まず、我らがくらしのマーケットのログインページはこちら。

くらしのマーケット

よくある感じですね、さて、置いていかれていないか調査に参ります。

世の中のログイン画面をみてみよう

Googleのログイン画面

Google

「メールアドレスまたは電話番号」と「パスワードを入力」する画面が別のページです。セキュリティ面の理由もありそうですが、ユーザーがログイン情報を覚えていないケースでは、この方が間違いがどちらにあるのか分かりやすそうですね。アプリでログインしようとしてもブラウザ版が起動するのも特徴です。「を入力」がパスワードの方にしか書いていないのは文字数が原因でしょうか。

Amazonのログイン画面

Amazon

「Eメールまたは携帯番号アカウントの番号」と「Amazonのパスワード」。 パスワードにわざわざ「Amazonの」と付いているのはなぜでしょう。 「アカウントの番号」なんてあるんですね、初めて知りました。

Facebookのログイン画面

Facebook

「電話番号またはメールアドレス」と「パスワード」の組み合わせです。シンプルで分かりやすく、今一番見かけるパターンな気がします。

Instagramのログイン画面

instagram

「電話番号、ユーザーネーム、メールアドレス」と「パスワード」の組み合わせです。サービスの特徴としてユーザーネームで覚えているユーザーが多そうですね。

LINEのログイン画面

LNE

「メールアドレス」と「パスワード」の組み合わせです。とってもシンプル!

Twitterのログイン画面

Twitter

「電話番号、メールアドレスまたはユーザー名」と「パスワード」の組み合わせです。Twitterもサービスの特徴としてユーザー名で覚えているユーザーが多そうですね。

Yahoo! JAPANのログイン画面

Yahoo! JAPAN

「ID/携帯電話番号/メールアドレス」と「確認コード」の組み合わせです。こちらも「ID」に当たる部分と「パスワード」に当たる部分の画面が別です。特徴としては、メールアドレスの場合でも、PCからのログインの場合でも、確認コードでログインする所です。パスワードを覚えなくていいのは便利ですね!

メルカリのログイン画面

メルカリ

まず最初に「アカウント連携」か、「メール・電話番号でログイン」かを選択します。 アカウント連携を選択するのユーザーが多いのでしょうか。「メール・電話番号でログイン」を選択後、「メールアドレスまたは電話番号」と「7文字以上の半角英数字」の組み合わせです。

UberEATSのログイン画面

UberEATS

「携帯電話番号」入力後、「OTPコードを入力してください」に4桁のコードを入力。SMSがうまくいかなかった時のために「パスワードを使用してログインする」が用意されています。こちらもブラウザ版が起動します。

TikTokのログイン画面

TikTok1

まず最初に「電話番号/メール/ユーザー名を使う」か、「アカウント連携」かを選択します。選べるアカウント連携の選択肢の数がすごいです!7つもあります! 「電話番号/メール/ユーザー名を使う」を選ぶと「ログイン」ページへ遷移し、タブで「電話」か「メールアドレス/ユーザー名」を選べます。

TikTok2

電話番号を入力すると4桁のコード入力画面に遷移しますが、こちらにも下の方に「パスワードでログイン」の導線がありました。クリックすると「パスワードを入力してください」の画面に行きます。SMSがうまくいかないケースが結構あるのでしょうか。

TikTok3

「メールアドレス/ユーザー名」のタブの中は「メールアドレスもしくはユーザー名」と「パスワード」の組み合わせでした。

TikTokは、ログインひとつに用意している選択肢の数が、圧倒的に多いですね!絶対ログインさせてあげるぞ!という気迫を感じます。

最後に

今回の調査で、「ログイン画面の当たり前」に若干遅れをとっていると感じました。「電話番号」が当たり前になりつつありますね。

「IDとパスワードは別のページ」という構成は、私の中でもっと多くのサービスの「当たり前」になっている印象でしたが、まだ「当たり前」にはなっていないのかなと感じた調査でした。

1,2年後に同じページの何がどう変化するのか、続編の調査も行いたいと思います!