はじめに
オブジェクト指向的な話は書きません。(コンストラクタでの初期化やインターフェースなど)
あくまで意識して書いといた方が便利みたいな内容です。
言語バージョン
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()
のほうは返り値の型を書いていない雑なコードなのにも関わらずコード補完が働いています。
レンチマークで候補として出ている文字列は静的解析した上で表示されているので、保証されている文字列であるとも言えます。
しかし、getUserArr()
は途中でキーが増えている影響でコード補完が効かなくなっています。
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
連想配列の方はサジェストとして、コード内の文字列からhealth
を拾ってきてくれていますが、これはあくまでサジェストであり、解析が行われていない文字列なのでコードの正確性を保証していません。
同じファイル内でhelth
などタイポがある場合それらも表示されてしまいます。
引数の増減が安全
例えば以下のような関数を呼び出している処理が複数あったとします。
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コメントを書きましょう。
/**
* @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ならデータクラスをコメント内に定義することも可能なのでより親切ですね。
/**
* 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を書くことで補完を効かせられるようになります。
これはマストだと思うので必ず書くようにしましょう。
/**
* 連想配列版を返す例
*
* @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;
}
まとめ
「そもそも型を書く意味って何?」という疑問に対して小難しいことを言うのは簡単ですが、それを聞いたところで活きてくるのはプロジェクトがある程度肥大化したあとだと思うので入口の説明としては不適切かなと思ってました。
この記事ではさっくりすぐ活きそうなメリットを列挙しました。