0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

型を書いておくと便利になるアレコレ

Posted at

はじめに

オブジェクト指向的な話は書きません。(コンストラクタでの初期化やインターフェースなど)
あくまで意識して書いといた方が便利みたいな内容です。

言語バージョン

PHP 8.3(なので8.4以降のように型厳密な環境はこの記事では考慮しません)
Node 23.7.0

型を意識すると何が得なのか

動的言語からプログラミングを始めた人が、型書いたほうがいいよって言われたときよくわからないのがこれかなと思ってます。
メリットがよくわからないんですよね。(そういう話でもないのですが)
個人的に短いエンジニア人生で感じたメリットを列挙してみます。

コード補完が受けられる(静的解析を受けられる)

極論これだと思っています。

実例としてPHPとJSで上げてみます。

<?php
class User
{
    public string $name;
    public int $age;
    public string $health;
    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

class Factory
{
    public static function getUserObj(string $name, int $age) // あえて返り値も書かない
    {
        $user = new User($name, $age);
        $user->health = "BAD";
        return $user;
    }

    public static function getUserArr(string $name, int $age)
    {
        $arr = ["name" => $name, "age" => $age];
        $arr += ["health" => "BAD"];
        return $arr;
    }
}

$userObj = Factory::getUserObj("John", 15);
echo $userObj->name;
echo $userObj->age;
echo $userObj->health;
// John15BAD

$userArr = Factory::getUserArr("John", 15);
echo $userArr['name'];
echo $userArr['age'];
echo $userArr['health'];
// John15BAD

どちらも結果は同じですが書いているときの感覚が違います。
getUserObj()のほうは返り値の型を書いていない雑なコードなのにも関わらずコード補完が働いています。
レンチマークで候補として出ている文字列は静的解析した上で表示されているので、保証されている文字列であるとも言えます。
type3.gif

しかし、getUserArr()は途中でキーが増えている影響でコード補完が効かなくなっています。
type4.gif

連想配列でも補完を効かせたい場合はこちら


JSも同様です。

class User {
  health;
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}


class Factory {
  static getUserObj(name, age) {
    const user = new User(name, age);
    user.health = "BAD";
    return user;
  }

  static getUserArr(name, age) {
    const userArr = { name: "John", age: 15 };
    userArr.health = "BAD";
    return userArr;
  }
}

const userObj = Factory.getUserObj("John", 15);
console.log(userObj.name);   // John
console.log(userObj.age);    // 15
console.log(userObj.health); // BAD

const userArr = Factory.getUserArr("John", 15);
console.log(userArr.name);   // John
console.log(userArr.age);    // 15
console.log(userArr.health); // BAD

こちらも型を定義している方は補完が効いています。
type1.gif

連想配列の方はサジェストとして、コード内の文字列からhealthを拾ってきてくれていますが、これはあくまでサジェストであり、解析が行われていない文字列なのでコードの正確性を保証していません。
同じファイル内でhelthなどタイポがある場合それらも表示されてしまいます。
type2.gif

引数の増減が安全

例えば以下のような関数を呼び出している処理が複数あったとします。

function sendEmail(string $address, string $name): void {}

このとき、引数を増やしたいと思ったとき非常にめんどくさいことがわかるでしょうか。

// 引数を一つ増やすだけで参照されている全ての処理を書き換えないといけない
function sendEmail(string $address, string $name, int uniqueId): void {}

しかし、引数を自作クラスで定義しておくことで追加が容易になります。
いきなり全て動かなくなるということがなくなります。

class Email
{
    public string $address;
    public string $name;
    // フィールドを追加してもエラーとならない
    public int $uniqueId;
}

function sendEmail(Email $email): void {}

可読性

その関数が何を必要としているか、何を返すのかが明確になります。

// 型無し(連想配列)
// 何が必要なのかがわからない
function greet($user) {
    echo "Hello, {$user['name']}!";
}

greet(['name' => 'Alice', 'age' => 30]);

// 型あり(クラス)
class User {
    public string $name;
    public int $age;
    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }
}

// この関数が何を必要としているのか明確になる
function greetUser(User $user): void {
    echo "Hello, {$user->name}!
";
}

greetUser(new User('Alice', 30));

型がかけなくてもdocコメントで補完しよう

いやそんなこと言っても言語バージョンだったり、環境のせいでそんなもん書けんよってときはdocコメントを書きましょう。

PHP
/**
 * @param string $name
 * @param int    $age
 * @return User
 */
function createUser($name, $age) { // 返り値型を書かなくても補完が効く
    return new User($name, $age);
}

$user = createUser('Alice', 20);
// $user-> で name, age, health が補完される

JSDocならデータクラスをコメント内に定義することも可能なのでより親切ですね。

JS
/**
 * docコメントで型を定義できる
 * @typedef {{ name: string; age: number; health: 'GOOD'|'BAD' }} User
 */

/**
 * @param {string} name
 * @param {number} age
 * @returns {User}
 */
function createUser(name, age) {
  return { name, age, health: 'GOOD' };
}

const u = createUser('Bob', 30);
// u. で name, age, health が補完される

以上のようにdocコメントをしっかり書いておくとコード補完も効くようになっていい感じです。

連想配列も補完を効かせられる

連想配列もdocを書くことで補完を効かせられるようになります。
これはマストだと思うので必ず書くようにしましょう。

php
    /**
     * 連想配列版を返す例
     *
     * @param string $name
     * @param int    $age
     * @return array{name: string, age: int, health: string}
     */
    public static function getUserArr(string $name, int $age)
    {
        $arr = ["name" => $name, "age" => $age];
        $arr += ["health" => "BAD"];
        return $arr;
    }

type5.gif

まとめ

「そもそも型を書く意味って何?」という疑問に対して小難しいことを言うのは簡単ですが、それを聞いたところで活きてくるのはプロジェクトがある程度肥大化したあとだと思うので入口の説明としては不適切かなと思ってました。
この記事ではさっくりすぐ活きそうなメリットを列挙しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?