公開日: 2020-05-08
更新日: 2020-05-27

ビューティフル・コード: 見やすいコードの書き方

ビューティフル・コード: 見やすいコードの書き方

プログラマにとって、コードが見やすいことはとても重要です。
例えば、インデントのずれでバグに気がついたり、レイアウト崩れに気がついたりします。
これらは書き方の美しさによって問題を早期発見できる典型的な例です。
わかりやすいさのために、Laravelをインストールすると最初から使えるUser.phpを例に説明します。
それでは、いってみましょう!

Laravelをインストール直後のUser.php

まず最初は、Laravelをインストール直後のUser.phpです。みなさんの環境でもこのようになっているはずです。

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
      * The attributes that are mass assignable.
      *
      * @var  array
      */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
      * The attributes that should be hidden for arrays.
      *
      * @var  array
      */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
      * The attributes that should be cast to native types.
      *
      * @var  array
      */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}
            

インデント幅をスペース2つにする。

最初にインデント幅をスペース2文字にします。
横幅を狭くして、横に2枚、ソースを並べて比較しやすくなります。
Laravelの場合、インストールすると.editorconfigというファイルが作成されます。
その中にindent_size = 4という記述があるので、これを2に変更します。

indent_size = 2

            
<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
  use Notifiable;

  /**
    * The attributes that are mass assignable.
    *
    * @var  array
    */
  protected $fillable = [
    'name', 'email', 'password',
  ];

  /**
    * The attributes that should be hidden for arrays.
    *
    * @var  array
    */
  protected $hidden = [
    'password', 'remember_token',
  ];

  /**
    * The attributes that should be cast to native types.
    *
    * @var  array
    */
  protected $casts = [
    'email_verified_at' => 'datetime',
  ];
}
            

スペース4つの場合と比べて、プログラムが引き締まった印象です。
HTMLを書く際にも、インデント幅がスペース4だと、横に間延びしてしまうので、私は2つが好きです。

固定値をconstで宣言する。

次に、固定値をすべてconstで宣言します。
固定値をconstで定義することで、エディタの入力補完機能で候補が出てくるようになります。
そして、値の直接入力を減らして、typoの発生確率を下げます。

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
  use Notifiable;

  const TABLE          = 'users';
  const ID             = 'id';
  const NAME           = 'name';
  const EMAIL          = 'email';
  const PASSWORD       = 'password';
  const REMEMBER_TOKEN = 'remember_token';
  const EMAIL_VERIFIED = 'email_verified';

  /**
    * The attributes that are mass assignable.
    *
    * @var  array
    */
  protected $fillable = [
    self::NAME,
    self::EMAIL,
    self::PASSWORD,
  ];

  /**
    * The attributes that should be hidden for arrays.
    *
    * @var  array
    */
  protected $hidden = [
    self::PASSWORD,
    self::REMEMBER_TOKEN,
  ];

  /**
    * The attributes that should be cast to native types.
    *
    * @var  array
    */
  protected $casts = [
    self::EMAIL_VERIFIED => 'datetime',
  ];
}
            

どうでしょう、コードが見やすくなったと感じませんか?

クエリをローカルスコープを使って組み立てる。

モデルの操作はローカルスコープを通じて行い、クエリの意味をわかりやすくします。

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
  use Notifiable;

  const TABLE          = 'users';
  const ID             = 'id';
  const NAME           = 'name';
  const EMAIL          = 'email';
  const PASSWORD       = 'password';
  const REMEMBER_TOKEN = 'remember_token';
  const EMAIL_VERIFIED = 'email_verified';

  const KEY             = self::ID;
  protected $primaryKey = self::KEY;
  const DELETED_AT      = 'deleted_at';

  protected $table      = self::TABLE;
  protected $dates      = [self::DELETED_AT];

  /**
    * The attributes that are mass assignable.
    *
    * @var  array
    */
  protected $fillable = [
    self::NAME,
    self::EMAIL,
    self::PASSWORD,
  ];

  /**
    * The attributes that should be hidden for arrays.
    *
    * @var  array
    */
  protected $hidden = [
    self::PASSWORD,
    self::REMEMBER_TOKEN,
  ];

  /**
    * The attributes that should be cast to native types.
    *
    * @var  array
    */
  protected $casts = [
    self::EMAIL_VERIFIED => 'datetime',
  ];

  public function scopeIdIsIn($query, array $ids)
  {
    $query->whereIn(self::TABLE.'.'.self::ID, $ids);
  }

  public function scopeNameIs($query, $string)
  {
    $query->where(self::TABLE.'.'.self::NAME, '=', $query, );
  }

  public function scopeNameIsLike($query, $string)
  {
    $query->where(self::TABLE.'.'.self::NAME, 'like', '%'.$string.'%');
  }

  public function scopeEmailIs($query, $string)
  {
    $query->where(self::TABLE.'.'.self::EMAIL, '=', $query, );
  }

  public function scopeEmailIsLike($query, $string)
  {
    $query->where(self::TABLE.'.'.self::EMAIL, 'like', '%'.$string.'%');
  }

}
            

こうすると、クエリを組み立てやすくなります。
例えば、名前がTaroでメールアドレスにgmail.comを含むユーザを検索する場合のクエリは以下のようになります。

$users = User::nameIsLike('Taro')
             ->emailIsLike('@gmail.com');

            

どうでしょう、わかりやすくありませんか?
SQLって、長く複雑になると、いったい何をしようとしているのか、後で見てもわからなくなってしまうことがあるので、ある程度のまとまりをもったクエリをグループにして、1つのメソッドで呼び出せるようにするのもいいと思います。

クエリをwhenでつなげる。

フォームから入力された名前やメールアドレスでユーザを検索するような場合、以下のようなコードを書く人が多いと思います。

$query = User::query();

$name  = $request->input(User::NAME);

if($name !== null) {
  $query->nameIsLike($name);
}

$email = $request->input(User::EMAIL);

if($email !== null) {
  $query->emailIsLike($email);
}

            

これを以下のように書き換えます。

$query = User::query();

$name  = $request->input(User::NAME);

$email = $request->input(User::EMAIL);

$query->when($name !== null, function ($query) use ($name) {
        $query->nameIsLike($name);
      })
      ->when($email !== null, function ($query) use ($email) {
        $query->emailIsLike($email);
      });

            

縦に揃える。

イコールやセミコロン、アロー演算子を縦に揃えましょう。

(A)

const TABLE = 'users';
const ID = 'id';
const NAME = 'name';
const EMAIL = 'email';
const PASSWORD = 'password';
const REMEMBER_TOKEN = 'remember_token';
const EMAIL_VERIFIED = 'email_verified';

            

(B)

const TABLE          = 'users';
const ID             = 'id';
const NAME           = 'name';
const EMAIL          = 'email';
const PASSWORD       = 'password';
const REMEMBER_TOKEN = 'remember_token';
const EMAIL_VERIFIED = 'email_verified';

            

(A)と(B)とどちらが見やすく感じますか?
私は(B)です。

(C)

$query = User::query();

$name = $request->input(User::NAME);

$email = $request->input(User::EMAIL);

$query->when($name !== null, function ($query) use ($name) {
  $query->nameIsLike($name);
})
->when($email !== null, function ($query) use ($email) {
  $query->emailIsLike($email);
});

            

(D)

$query = User::query();

$name  = $request->input(User::NAME);

$email = $request->input(User::EMAIL);

$query->when($name !== null, function ($query) use ($name) {
        $query->nameIsLike($name);
      })
      ->when($email !== null, function ($query) use ($email) {
        $query->emailIsLike($email);
      });

            

(C)と(D)とどちらが見やすく感じますか?
私は(D)です。

少しの工夫で、コードを見やすくできることがおわかりいただけたでしょうか?
コードはプログラマにとって、自己表現の手段です。
日頃から美しいコードを書くことに注意を払いましょう。