NAKKA-Kの技術ブログ

技術に関する知見や考え方などを投稿します。

vimのGoの言語補助プラグインでUnknown functionバグが発生した

vimをupgradeさせた時に唐突にvimがエラーを吐き出し始めました。 エラーは吐き出すもののとりあえず動いてはいました。 ですがvim-goのある部分でエラーが発生しているようでした。

Error on startup: Unknown function: go#config#CodeCompletionEnabled

とりあえずvim-goのissueを探していると今回の問題に関係ありそうなissueを発見しました。

github.com

このissueに書いてあることを要約すると「sheerun/vim-polyglotから使用される言語系プラグインを、vim-polyglotより先に読み込んではいけない。」というものでした。 vim-polyglotを最後に読み込むようにコードを書き換えて再度読み込みすれば解決しました。 私の場合はdein.vimを使っていたのでキャッシュを削除してもう一度プラグインのインストールを走らせる必要がありました。

webアプリケーション開発で個人的に重視しているテストの話

Slackでテストの話について盛り上がったので、webアプリケーション開発で個人的なテスト毎の優先順位などについて書きたいと思います。 早速本題に入りたいと思います。

Controller

コントローラーの処理をホワイトボックステストする利点は少なく、ブラックボックステストの方がいいと思います。

特にE2Eテストはとんでもなく変更コストが高いので割と否定派です。 サービスの根幹をなす部分に関してのみ絶対の信頼を担保したい場合書く、というくらいでいいと思います。

E2Eテストについて書いた記事もありますので詳しく見たければそちらもご覧ください。

qiita.com

コントローラー周りのテストとして、アクセスして期待したstatus(200番など)か観測する、という単純なテストならコストがかなり低くメリットもあると思っているので割と書くことが多いです。

Model

やはりwebアプリで一番重要なテストはモデルのテストです。

システムは全ての箇所でモデルを操作することになりますし、システムで一番重要なのはいかにデータを操作するかです。 ですのでモデルをテストするだけで全体のテスト網羅率は跳ね上がり正確性を担保できます。 モデルテストを全然書かないシステムは最悪です。 一番根幹の部分が担保されていないシステムはガタガタになってしまうからですね。

View(UI)

UIテストに関してはほぼ書くことはありません。

ごくごく偶に書かれることはありますが、さほど役立った経験はありません。 Railsなどにおいてはまず書かないものとしてプラクティスが出来上がっています。

Unit

単体テストに関してはwebであろうとなんであろうと同じ程度に重要です。

しかし外からは見えないはずのpublic以外のメソッドをテストするのはほぼ死刑です。 カプセル化を破壊せずにpublicメソッドを通じてブラックボックステストしてください。

まとめ

テストの優先度としては、モデルテスト > アクセスのテスト > もう少し詳細なコントローラーテスト > E2Eテスト > UIテスト、という形に落ち着くかと思います。 単体テストはいろんなところで発生するものなのでここでの順位付けはしませんでした。

テストといえばTDDについての話もありますがそれについてはここでは触れませんが、webアプリケーションの作成でTDDというのは割と上級者向けかなと個人的に思っています。 TDDについては過去におすすめのTDD本について書いているのでそちらをご覧ください。

nakka-k.hatenablog.com

Laravel5.7 [API開発]で権限設定をPolicyに任せて幸せになろう

LaravelでAPIを作っていると、例外周りの処理を良い感じにしてくれないので非常に大変です。 Policyをそのまま使っても403ページが返ってしまいますし、エラーメッセージの変更も困難です。 今回は権限判定をPolicyに移植して、かつ任意のエラーメッセージをjsonで返せるようにした手順をご紹介します。

FormRequestについての記事もあります。 nakka-k.hatenablog.com

Policyとは

Laravelには特定のモデルやリソース、処理に対して権限を判定し認可・非認可する方法があります。 その方法には大きく分けてGateとPolicyの2つがありますが、今回はPolicyについてのみ説明します。

Policyとは、モデルなどのデータに対してユーザーが行った動作を認可・非認可するために使われる仕組みです。 例えばPostモデルを編集する権限はPostを投稿したユーザーのみにしかない、といった権限判定に使われます。

今まではこんな書き方をしていた

ソースコードは一部改変しています

ユーザーそれぞれが持つ本棚に対して設定を変更する処理を書いているコントローラーです。 コントローラー内で編集先のユーザーIDとログインしているユーザーIDを比較して、許可されていない編集だと判定した場合に403のjsonレスポンスを返しています。

class UserBookController extends Controller
{
    public function update(Request $request,  $user, $userBookId)
    {
        // 認可チェック
        if(auth()->guard('api')->id() != $user->id){
            return response()->json(
                [
                    'status' => 403,
                    'errors' => ['自分以外の本棚を編集することはできません。']
                ],
                403
            );
        }

        // success authorized ......
    }
}

コントローラー内でいちいち認可チェックを書いていてはコントローラーが肥大化しますし、コードを間違えて本来許可されていない動作を許してしまう可能性が増えてしまいます。 やはり認可は認可だけ別の場所に移動させてしまいたいですね。

Laravelでモデル操作の認可に関する処理を書く場合はPolicyを使うべし、と公式ドキュメントに書かれていたのでPolicyを作っていきましょう。

Policyを作成する

Policyはモデルと1対1で結びつく物です。 基本的なCRUD操作に対してそれぞれ認可判定を書けるようになっています。

ファイル生成

今回はUserBookというモデルに対してPolicyを生成しますので--modelオプションでモデルを指定しています。 実際に生成されたメソッドを全て使うわけではありませんが、最初に全体を見ていた方が分かりやすいので生成した後に使わないメソッドを消していく手法を取っています。

$ php artisan make:policy UserBookPolicy  --model=UserBook

もちろん--modelオプションは付けずとも作ることができます。

コマンドを実行するとapp/Policies/が作成され、その下にこのようなファイルが作成されます。

思った以上にメソッドが多いですが簡単にまとめると以下のような役割になっています。

  • view: 閲覧
  • create: 作成
  • update: 更新
  • delete: 削除
    • 論理削除を実装している場合、物理削除ではなく論理削除する時
  • restore: 論理削除状態から復元する時
    • 論理削除を実装している場合のみ
  • forceDelete: 物理削除
    • 論理削除を実装している場合のみ

処理記述

今回はupdateを実装していきます。

Policyの各メソッドはBoolean返す必要があります。 認可だとtrue、非認可だとfalseを返すため条件分をそのまま戻り値に書いてあげましょう。 もちろんifで制御しても良いです。

    public function update(User $user, UserBook $userBook)
    {
        return $user->id == $userBook->user_id;
    }

次はPolicyをモデルに紐づけて認可チェックをしてくれるように設定しましょう。

Policyを組み込む

まずapp/Providers/AuthServiceProvider.phpを編集します。 このファイルには$policiesという連想配列を持つ変数があります。 そこにモデルとPolicyの対応づけを追加しましょう。

    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
        'App\UserBook' => 'App\Policies\UserBookPolicy'
    ];

そして追加したPolicyを反映させるために、コントローラーの__constructミドルウェアの設定をします。 今回はコントローラーでミドルェアの設定をしますがどこで設定しても構いません。(参考)

class UserBookController extends Controller
{
    public function __construct(){
      $this->middleware('can:update,userBook')->only('update');
    }

これによりUserBookモデルのDI時にUserBookPolicyの認可チェックを走らせ、場合によって403のレスポンスを返してくれるようになります。

ですがここで問題が発生します。 基本的にLaravelの例外処理はhtmlページを返すようになっています。 これではAPIとして問題がありますので、次はjsonを返すようにしていきましょう。

エラーハンドリングのためにLaravelのコードリーディングをする

はい。まず結論から申しますとPolicyにレスポンスを動的に記述するメソッドは見つかりませんでした。 いろいろ調べてもよく分からなかったので軽くLaravelのコードリーディングをしてみましょう。

Policyのファイルを見てみるとuse HandlesAuthorization;というトレイトをuseしている行があります。 Policyクラスは継承などをしていないようなので、ほぼ全ての動作がこのuse HandlesAuthorization;に集約されていそうです。 このトレイトはIlluminate\Auth\Accessネームスペースのようなので、まずそのファイルを見てみましょう。

gist.github.com

allowdenyという2つのメソッドだけありますね。 なんとなく認可・非認可に使われるメソッドのようですし、失敗した時はdenyにエラーメッセージが渡されているようです。

これだけでは全然わかりませんね。 まずはdenyメソッドの戻り値であるAuthorizationExceptionを見てみましょう。

gist.github.com

なんというか何も書いていませんね。 でもまあException(例外)であることだけは分かります。

ではもう一度HandlesAuthorizationに戻って、次はallowメソッドの戻り値であるIlluminate/Auth/Access/Responseを見ていきましょう。

gist.github.com

戻り値のラッパーでしかなさそうですね。 とりあえずスルーしましょう。

んー、なんだか遡れそうな先がなくなりました。 では同じnamespaceにあるまだ見ていないファイルも見てみたいので同じディレクトリにあるIlluminate/Auth/Access/Gateも読んでみましょう。

(ファイルが大きすぎるのでリンク先でご覧ください。)

とりあえずHandlesAuthorizationで使われていたallowdenyの呼び出しとかがないか調べてみましょう。

なんだかauthorizeというメソッドに使われている痕跡がありますね。 レスポンスの型も同じですね。

    /**
     * Determine if the given ability should be granted for the current user.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return \Illuminate\Auth\Access\Response
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function authorize($ability, $arguments = [])
    {
        $result = $this->raw($ability, $arguments);
        if ($result instanceof Response) {
            return $result;
        }
        return $result ? $this->allow() : $this->deny();
    }

ここからもいろいろ遡れますがあまり有益な情報は得られませんでした。 私のLaravelのコードリーディング力の無さが露見しますね。

一番有益な情報もありました。 AuthorizationExceptionにメッセージを渡してthrowすればなんとなく上手くいきそうな気がします。

ではその方針で進めていきます。

エラーをjsonで返すようにする

まずはjsonを返すようにしましょう。 Policyで非認可されればAuthorizationExceptionが帰ってくるようなので、その例外をキャッチしてjsonを返すように処理を書きます。

レスポンスの最終的なエラーハンドリングを変更するにはapp/Exceptions/Handler.phprenderメソッドに追記していきましょう。

    public function render($request, Exception $exception)
        if ($exception instanceof AuthorizationException) {
            return response()->json([
                'errors' => [$exception->getMessage()]
            ], 403);
        }

        return parent::render($request, $exception);
    }

例外である$exceptionの型を判定してAuthorizationExceptionであればjsonを返すように記述しましょう。 jsonにエラーメッセージを含めるために$exception->getMessage()をレスポンスに入れましょう。 ここで返されるメッセージはHandlesAuthorizationdenyメソッドのデフォルト引数だった'This action is unauthorized.'です。

次はエラーメッセージを動的に変更できるようにしましょう。 Policyのファイルを編集します。私の場合app/Policies/UserBookPolicy.phpですね。

以前は条件文を返すだけだったのでBooleanでしたね。 ここでAuthorizationExceptionを返すようにしてしまいます。

    public function update(User $user, UserBook $userBook)
    {
        if ($user->id == $userBook->user_id) {
            return true;
        }
        throw new AuthorizationException('自分以外の本棚を編集することはできません。');
    }

認可する場合は今まで通りtrueを返してあげますが、falseを返していたタイミングではAuthorizationExceptionthrowします。 ここで投げられたAuthorizationExceptionは先ほど書いたエラーハンドリングメソッドであるrenderメソッドでキャッチされます。 AuthorizationExceptionに渡した引数が$exception->getMessage()で呼び出されるメッセージになります。

実際のレスポンスを見てみましょう。

{
    "errors": [
        "自分以外の本棚を編集することはできません。"
   ]
}

想定通りのレスポンスになっていますね! ということはこれで、jsonを返しつつエラーメッセージも動的に変更することができました!!

最終的にできたコード

https://gist.github.com/NAKKA-K/3d296424fd0c2568ceb6f5d91157c8a5

PolicyとFormRequestを併用

今回の記事では殆ど出てきませんでしたがFormRequestと言う、POSTなどで送られてきたデータをバリデーションしてくれる機能があります。 FormRequestとPolicyを併用した場合、しっかりとPolicyで権限をチェックした後にリクエストのバリデーションをしてくれます。 なので安心してFormRequestとPolicyを併用してください。

FormRequestの導入方法は以下の記事をご覧ください。

nakka-k.hatenablog.com

まとめ

やはり権限周りのチェックはかなり重要な機能です。 確実に権限判定の処理を書くためにPolicyに責任分離し、しっかりと実装しておきましょう。

Policyに分離することで責任分離もできますし、コントローラーの記述量がかなり少なくできます。 どんどん使っていきましょう。

参考

Laravelを使ったAPI開発でController内のバリデーションをFormRequestに抽出して幸せになろう

私の開発しているプロジェクトでは

  • Laravel 5.7
  • React/Redux

を使って開発しています。

バックエンドのLaravelはAPIを実装しています。 LaravelでAPIを実装すると通常のwebで作るより処理が煩雑になる気がします。 その上、いろんな処理をControllerにまとめて書いてしまった経験はそれなりにあるのではないでしょうか?

今回はリクエストのバリデーション処理をControllerに書いている状態から、LaravelのFormRequestを使って責務を抽出した手順をご紹介します。

(軽く調べた感じLaravel5.5以降でないと違う書き方になるようです)

Policyについての記事もあります。

nakka-k.hatenablog.com

FormRequestとは

FormRequestとはリクエストのバリデーションルールを定義するLaravelの仕組みです。 FormRequestをコントローラーメソッドにDIしてあげると、バリデーションが通った時だけコントローラー内の処理が走ります。

これによりコントローラーはリクエストに不正な値が入っている可能性を除外できるため、自分の処理だけに専念できます。

Controllerにバリデーションを書いていた時

APIでバリデーションを書くと割と面倒でした。 例えばこれはログインする時に書きそうなバリデーションです。

(after内の処理にいい例がなかったので、ちょっと特殊な例になってしまいましたがあまり気にしないでください)

    public function login(Request $request) {
        $validator = \Validator::make($request->all(), [
            'email'    => 'required|string|max:64',
            'password' => 'required|string|max:64',
        ]);

        $validator->after(function ($validator) {
            $user = \App\User::where('name', $this->input('name'))->first();

            // 同名ユーザが存在して、ログイン中ユーザと同ユーザであればエラー
            if(null !== $user && $this->user()->id === $user->id){
                $validator->errors()->add('name', '既にログインしています。');
            }
        });

        if ($validator->fails()) {
            return response()->json([
                'status' => 400,
                'errors' => $validator->errors()
            ], 400);
        }

        // request is valid ......
    }

ほぼ全てのPOST系コントローラーにこれと同等な処理が書かれていました。 一律でエラー時のレスポンス構造を設定する方法などもありそうですが、開発初期だったためこの書き方で統一していました。

コントローラーにバリデーションを書いてしまうと、常にコントローラーがバリデーションについて責任を持たなければなりませんし、約10行近い処理がメソッドを占領してしまいます。

これは非常によくありません。

そこでバリデーションをFormRequestに置き換えることになりました。

FormRequestを作成する

まずArtisanCLIコマンドのmake:requestを使用して、FormRequestのファイルを作成しましょう。 今回はログイン時のバリデーションをしたいのでLoginRequestとしましょう。

php artisan make:request LoginRequest

app/Http/Requests以下にLoginRequest.phpファイルが作成されます。 このファイルに先ほどコントローラーに書いていたバリデーションを移植します。

まず作成されたファイルはこのようになっています。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

ここで一番気をつけるべきなのはpublic function authorize()です。 デフォルトでfalseを返すようになっていますが、特にアクセス権限などを求めないリクエストであればtrueを返すように書き換えてください。

バリデーションルールを定義

ではバリデーションを移植していきましょう。 public funciton rules()がありますね。 ここにバリデーションのルールを書きます。

    public function rules()
    {
        return [
            'email'    => 'required|string|max:64',
            'password' => 'required|string|max:64',
        ];
    }

ルールはこれでOKです。

ですがこのままではバリデーションエラーが発生した時にHTMLを返してしまいますので、JSONを返すようにメソッドをオーバーライドしていきます。

レスポンスを定義

function failedValidation(Validator $validator)を定義します。 ここではバリデーションを走らせて失敗した後のvalidatorが引数に渡されます。 ですから$validator->errors()などを駆使してレスポンスのjsonを生成しましょう。 そして戻り値にはHttpResponseException(response)を返します。

    protected function failedValidation(Validator $validator) {
        $res = response()->json([
            'status' => 400,
            'errors' => $validator->errors(),
        ], 400);
        throw new HttpResponseException($res);
    }

afterなどの追加処理を定義

$validator->after()も移植しましょう。 function withValidator(Validator $validator)を定義します。 ここではバリデーションが実行される前に呼び出されるので、validatorを使って何か事前処理を書きたい場合に使います。

    public function withValidator(Validator $validator) {
        $validator->after(function ($validator) {
            $user = \App\User::where('name', $this->input('name'))->first();

            // 同名ユーザが存在して、ログイン中ユーザと同ユーザであればエラー
            if(null !== $user && $this->user()->id === $user->id){
                $validator->errors()->add('name', '既にログインしています。');
            }
        });
    }

完成したFormRequest

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;


class LoginRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'email'    => 'required|string|max:64',
            'password' => 'required|string|max:64',
        ];
    }

    protected function failedValidation(Validator $validator) {
        $res = response()->json([
            'status' => 400,
            'errors' => $validator->errors(),
        ], 400);
        throw new HttpResponseException($res);
    }

    public function withValidator(Validator $validator) {
        $validator->after(function ($validator) {
            $user = \App\User::where('name', $this->input('name'))->first();

            // 同名ユーザが存在して、ログイン中ユーザと同ユーザであればエラー
            if(null !== $user && $this->user()->id === $user->id){
                $validator->errors()->add('name', '既にログインしています。');
            }
        });
    }
}

FormRequestを組み込む

作成したLoginRequestをコントローラーに組み込んでバリデーションに使用してもらいましょう。 コントローラーメソッドの引数にあったRequestLoginRequestに変更するだけです。

use App\Http\Requests\LoginRequest; // インポートを忘れずに


    public function login(LoginRequest $request) {
        // request is valid ......
    }

なんということでしょう! あの煩雑で責務の混ざり合った見るたびに嫌気のするコントローラーが、こんなにも開放的で責務の分離された美しいメソッドに生まれ変わりました! これには開発者さんも幸福感を隠しきれません。

まとめ

Controller内のバリデーションをFormRequestに抽出するだけ多くのメリットをもたらします。 何か事情がなければ最初からFormRequestを使った方がいいですね。

プロジェクトで途中からFormRequestを使うようにした場合やLaravel初学者が多い場合は、一律でFormRequestを使うように周知しておくのをお勧めします。 もし抽出後の綺麗なコントローラーに誰かがバリデーションをそのまま追加してしまった日には

なんということをしてくれたのでしょう! あの開放的で責務の分離された美しいメソッドが、煩雑で責務の混ざり合った見るたびに嫌気のするコントローラーに逆戻りです! これには開発者さんも般若の顔を隠しきれません。

と叫ぶことになってしまいますからお気をつけください。

参考

Raspbianの固定ローカルネットワークアドレスの設定

IPの設定

まず/etc/dhcpcd.confを開きます。

ここで固定IPを設定します。

interface eth0
static ip_address=192.168.10.33/24
static routers=192.168.10.1
static domain_name_servers=192.168.10.1 8.8.8.8

ip_addressは設定したいRaspberryPiのIPアドレスrouterは同ネットワークのルーターIPアドレスdomain_name_serversDNSと呼ばれるもので名前解決するサーバーのIPアドレス

設定が通らない時はいくつかのパターンがあります。

  • ルーターIPアドレスが間違っている。 -サブネットが間違っている。
  • 物理的にネットワークに繋がっていない。
  • domain_name_serversの指定が間違っていたりして、/etc/resolve.confに正常な設定がされない。

確認

まずはネットワークが設定されているか確かめましょう。 適当な所でCtrl+cを入力して終了しましょう。

$ ping 8.8.8.8

PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=49 time=10.9 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=49 time=5.33 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=49 time=5.52 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=49 time=5.46 ms

^C
--- 8.8.8.8 ping statistics ---

もしここでping: icmp open socket: Operation not permittedといったエラーが発生した場合はpingコマンドの設定をしましょう。

$ sudo chmod u+s /bin/ping

これで繋がります。

SSH

RaspberryPi3にSSHする場合、専用の設定をしておかなければSSHができません。

これについては簡潔な外部記事(1分程度)を貼っておきますのでそちらをご覧ください。

まとめ

以上で設定は終了です。

同じネットワークに属す別のPCからSSHしてみましょう。 ユーザー名など変更していないのであればpiのままです。

$ ssh pi@192.168.10.33

yes/noの質問が出たらyesを入力しておけば大丈夫です。

Reactのプロジェクトを平成最後の大リファクタリングした話

平成の技術的負債を令和に持ち越したくないからリファクタリングしよう!!と思ったのが事の始まりでした。

短期的に開発速度を求めLintを後回しにしてしまったプロジェクトにLintツールを導入し、Reactのコードを全て綺麗にした時のツールや方法を書きます。

Lintとは

ソースコードを静的にチェックし、書式やバグの原因になるような曖昧な記述について警告してくれるプログラムの事です。
Lintツールを使うと実行時エラーなどとは違ってコードを実行する前に警告を確認できるため、常に綺麗な状態を保つことができます。

やりたいこと

前提としてプロジェクトはReact+Reduxで、ECMAScriptで記述されています。

  • コード整形
  • 構文チェック
  • コミット時の自動Lint
  • プロジェクト全体の警告状況を可視化(Lintエラー数などがグラフで見れると嬉しい)

導入したツール

JavaScriptコードのLintが必要なのでどんなツールがいいのか色々調べました。 するとESLintPrettierという2つのツールがよく使われているようです。

ESLintはコード整形はもちろん他にも多くのチェック機能が付いています。そしてPrettierはコード整形に特化したツールです。 ESLintだけで事足りそうですがPrettierを併用する理由としては、Prettierの方がコード整形が上質で、しかも簡単に設定・実行できるからです。

という訳で、ESLintとPrettier、そしてそれらを併用するための周辺ライブラリを導入します。

  • prettier
    • 整形ツール
  • eslint
    • 整形ツールを含めたLintツール
  • eslint-config-prettier
    • ESLintとPrettierを繋ぐライブラリ
  • eslint-plugin-prettier
    • ESLintとPrettierを繋ぐライブラリ
  • eslint-plugin-react
    • ESLintでReactのコードを検査するためのライブラリ
  • eslint-nibble
    • Lintエラー数や種類をグラフで可視化できるツール(通常はなくても困らない)
$ npm install --save-dev prettier eslint eslint-config-prettier eslint-nibble eslint-plugin-prettier eslint-plugin-react

次にコミット時に自動Lintできるように追加のライブラリを導入します。

  • husky
    • Gitのcommitやpushに対して処理をhookするライブラリ
  • lint-staged
    • Gitにステージングされたファイルに対してLinterを実行するライブラリ
$ npm install --save-dev husky lint-staged

これでとりあえず必要なライブラリは全て導入しました。 あとはやるだけですね。

※ESLintやPrettierの設定を見たい方はこちらをご覧ください。

現状確認

まずは現状を把握するために入れた可視化ツールを使ってみましょう。

プロジェクトのルートでnpx eslint-nibble ./を実行します。

プロジェクトのLintエラーを可視化したグラフ
プロジェクトのLintエラーを可視化したグラフ

ぴったり400個ものエラーがありますね。おぞましい数です......。

さすがにこれを一気に修正するには規模が大きすぎて脳のリソース的にも、影響範囲的にも難しそうです。 少しずつ修正を加えていきましょう。

解決

基本的にはLintツールを走らせれば、整形だけで対応できるコードは自動的に修正してもらうことができます。

$ npx eslint file.js --fix

このコマンドを実行すれば指定したファイルにLintを実行し、発生したコード書式の問題などは自動で修正しファイルを上書きしてくれます。

しかし、コード書式だけならこれで問題ないのですが自動で修正できないLintエラーも存在します。

ReactのコンポーネントにはpropTypesという、引数のようなものに型を指定する方法が存在します。 この型指定はしてもしなくても実行に差し支えはありませんが、指定した方がコードのバグチェックなどにも役立つため書くことを推奨されています。 これらはLintツールが自動で修正することはできませんので、自分で該当箇所を見て型を追加する必要があります。

その他には、使っていない変数やimportされた物がある場合もエラーが発生しますが、自動修正はされません。

であればコード整形の他に、忘れられた変数たちや型の定義を自分でしていく必要がある訳ですが、広範囲に渡る修正のためどこから手をつけていくか考える必要がありそうです。

そんな時はまずeslintコマンドに--fixオプションを付けずに実行してみましょう。 そうすれば、Lintエラーの一覧だけが表示され修正の上書きはされません。

$ npx eslint file.js

エラーの内容を見ていくと、書式系のエラーと自動修正できなさそうなエラーがある程度わかります。 まず影響範囲の少ないファイルやディレクトリから順番にチェックしながらエラーを修正していきます。

私の場合は小さなコンポーネントディレクトリから順番に攻めました。 ディレクトリ以下を一括でチェックする場合は以下のようにすれば良いです。

$ npx eslint AComponent/*/*

ここでいきなりfixを付けるよりかは、一旦エラーの数や度合いをチェックした方が良いです。 もし自動修正できないエラーの数が多いようであれば、チェックする領域を狭めながら一度に修正しやすい範囲に押さえましょう。 そうでないと1コミットの修正量が異常な数になったり、同時に直す箇所が多すぎて手が回らなくなったりする可能性があります。

一度に修正する範囲が決まったらその範囲だけを--fixオプションを付けたeslintコマンドで自動修正しましょう。 その後、残ったエラーを手動で解決していくと良いです。

$ npx eslint AComponent/*/* --fix

一度に修正すると決めた範囲の修正が終わるごとにコミットしましょう。 毎回コミットをせずにまとめてやってしまうと、コードレビューの時にレビューの手が回らなくなってしまいます。 修正する時に手が回らない範囲をコミットしてしまうと、コードレビューの手が回るわけがありませんから。

コミットと同時にプルリクエストの規模が大きくなりすぎるのも問題です。 流石に1コミットごとにプルリクエストを出すわけではありませんが、1~200行程度、多くとも1000行以下で一度プルリクエストを出して欲しいです。 こちらもコードレビューがひたすら辛くなってしまいますし、一度の影響範囲が大きくなりすぎて他の作業者と競合してしまう可能性が高くなってしまいます。

後はこれらのことを気をつけて地道に修正していけばその先に待つのはなんのLintエラーも無い、すっきりとした開放的な環境です。

修正完了

Lintエラーを全て解消した!
Lintエラーを全て解消した!

Lintエラーを全て解消しました!!

最後に、これからはLintエラーを含んだコードをリポジトリに混入させないよう、コミットにhookしてLintエラーを自動修正・エラー出力・問題がある場合にコミットを取り消しするように設定します。 package.jsonに設定を追記しましょう。

    "scripts": {
        "precommit": "lint-staged"
    },
    "lint-staged": {
        "*.{js,jsx}": [
            "eslint ./ --fix",
            "git add"
        ]
    },

これでLintエラーを含んだままコミットしようとしてもリポジトリが汚されることはありません!

ああ、なんと素晴らしいことでしょうか......。

まとめ

このプロジェクトの規模はjs・jsxファイルを合わせて70ファイルで5000行程度でした。 この程度のプロジェクトでも1.5人日(約12時間)程度かかりました。

おそらく業務コードや長期間運用するコードはこれどころではない規模になってしまうと思います。 Lintツールを導入するのにはそこまで時間がかかりませんし、最初から導入していれば後から面倒な修正作業も発生せず、常に綺麗なコードを保つことができます。

迷ったらとりあえずLintツールをいれましょう。

さもなくば後から発狂することになりますよ......。

参考

資格を取るべきなのか専門学校生としての経験をもとに考えてみた

最近Twitterでは基本情報やらITパスポートやら資格についての話がよく見受けられます。

資格をかなり重要視している専門学校の、生徒だった経験から資格をとった方が良いのか、取らなくても良いのか、どのような心構えで取るべきなのかを個人的な考えで書いていこうと思います。

ここ最近で話に上がっている資格

(補足)

各資格について少しだけ補足しておきます。

ITパスポート

パソコンを扱う人なら分かっていた方が良い資格です。

基本情報技術者

プログラマはもちろんですが、ITに関係する人であればこの資格の内容はわかっておいた方が良いです。

ITパスポートの単純上位互換に近く、PCに関する話からマネジメント、金銭の話まで幅広く出題されます。 とりあえずこの資格の内容を理解していれば最低限IT人材と言えるかと思います。

応用情報技術者

しっかりとしたIT人材としてやっていくには最低限ここまで理解しておくべきだと思います。 資格としてはそれなりに面倒ですが、しっかりと理解すればさほど難しい資格ではありません。

資格自体が必須になるタイミング

資格を持っているということのみでフィルタリングされる場合があります。

例えばSIer系(?)でプロジェクトに配備される人の条件として、基本情報を持っている人が一定数必要である場合などがあります。 国のプロジェクトではこのようなフィルタリングをされる可能性が高いです。

また就職活動をする際に基本情報などの資格を持っていることが応募条件である場合もあります。

よく聞くのはこのくらいです。

Twitterでよく見る意見

賛成派

  • 資格を持っていることで一定の知識を担保できる
  • 取っておいて損はない

否定派

  • 資格勉強しかできない頭でっかちが多い
  • 資格を取るくらいならプログラミングした方がいい

全体として

やはり賛否両論という感じです。

「知識ベースの資格には意味がないが、実用的なその他に分類される資格には意味がある。」といった資格によって賛否が違う内容もよく見かけます。

資格で本当に知識を担保できるのか?

資格を取るためには一定以上の勉強をしなければいけません。 となればもちろんその資格分の知識は勉強したのでしょう。

ですが、資格を取ると言った時に、どの様に勉強をしているのでしょうか?

資格を取るためだけに勉強して、実際に取った後はほとんど忘れてしまっているなんてこともよく聞きます。 しかも実技はからっきしで頭でっかちでなおタチが悪いなんて人をよく見ますね。

こんな人が多い状態で本当に知識を担保できるのかというと少し疑問があります。

資格は必要ない

個人的は資格自体が必須になる場合以外で、資格は一切必要ないと思っています。

なぜか?

わざと過激な言い方をします。

資格を取るためだけに勉強した知識などほとんど覚えている人はおらず、それで頭でっかちになってしまうのであれば取らない方がマシだと思うからです。 そしてそんな勉強は時間の無駄です。 それこそプログラミングをしていた方が時間の有効活用になります。

資格の有用性

それでも資格には一定の有用性があります。

資格を取るためだけに勉強して知識がほとんど抜けていたとしても、その資格を取るために努力した事実はなくならないのです。 その資格が難しいものであるほど、その人はそれだけの努力をしたのです。

それだけ努力ができる人だと認識できる一点においては非常に有用だと思います。

私が資格の学習をする理由

ここまで資格を否定しておいてなんですが、私は資格の学習をすることには賛成します。 私自身ITパスポート〜応用情報まで取得していますし、ネットワークスペシャリストも受けていました(後4点で落ちましたけど!)。

ここで一番言いたいことは、「ただ資格を取るために勉強する行為にはほぼ意味がないが、知識を習得するために学習しそのゴールとして資格取得を目指すことには大いなる意味がある」と言うことです。

専門学校なんてのは特にそうでしたが、資格を取らせることを大きな目標にしています。 なぜなら資格取得率が重要であることと、資格によるフィルタリングを通り抜け就活を有利にするためと、資格を取ることで少しでも知識を覚えさせたいからです。

しかし専門学校にいる人はもちろん、資格を取るためだけに勉強する人のなんと多いことか。 それでは資格のために勉強したことはほとんど血肉にならないじゃないか。

私はゲームでも称号を無駄に集めたがる性格なので、そう言う理由で資格が取りたいとも思っています。 なので資格を取りたいと思うことはなんら問題ありません。

その資格を取るときの学習姿勢が一番重要なのです。 その姿勢を間違えなければ学習した全ての内容が血肉となり、得た知識が技術の向上に直結します。

まとめ

何度でも言いたいです。

資格を取ることでも資格の得点を取ることでもなく、己の研鑽のために学習をしてほしい