見出し画像

大人数でのプログラム開発に大事なこと

大人数での開発に慣れていない人が急に大きなプロジェクトを経験すると、ちょっと失敗したようなコードになります。
それ自体は初心者ゆえなのだからしょうがないのですが、だからといって自身のコードの欠点に気づくこともなく経験年数だけがすぎてしまうと、やっぱり周囲からの評判を得ていくこともできないでしょう。

そこで今回は、そういう人が気をつけるべきことを紹介します――。

いつもお読みいただきありがとうございます。
または初めての方も、この記事を見つけてくださって嬉しいです。

私は、昨今の混沌としたIT業界の今後について深く考えたり考えなかったりしております、エンジニアの中島と申します。
この 絶対バグらないシステム作ろうぜの会 では、バグの出ないシステム・問題を起こさないチーム運営・AIには作れない設計論などのコラムを、なるだけ面白く・分かりやすくお伝えする主旨で記事を配信しています。


1. 大人数での開発のために学ぶべきことの例

私は個人開発とチームでの開発の両方を割と日々やっている立場なわけなので、その違いみたいな部分は強く感じることが多いです。
その中でも、ここ数年で強く大事だと思ったことを紹介したいと思います。

1. ロジックの説明には必ず “何を” “どうする” 構文を使うこと

アルゴリズムやロジックを文章で説明する際、その文章は常に必ず
 “何を” “どうする”
という形にできるはずです。
なぜならコンピューターは “情報を” “処理する” ための装置だからです。

これはロジックが複雑な場合も同じです。

  • 何を、どうする。

  • そのために、何を、どうする。

  • 次に、何を、どうする。

  • その結果、何が、どうなる。

全てのアルゴリズムは、説明を簡潔にしていくと必ずこのような構文に落ち着きます。
それができないとしたら、それは説明をちゃんと頭の中でまとめきれてないからです。

2. インプットは全て疑う

プログラムにおいて、全てのメソッドは『何らかの情報を入力し、加工して結果を返す』という動作を行います。
ですがこのとき、『入力された情報が正しくない』という状況は往々にして発生するものです。

これは、あなたが作ったメソッドでも変わりません。

  • あなたが作ったメソッドを、他者が中身を見ずに適当に使った

  • 時間がたっていたので中身の仕様を忘れ、適当に使った

このような理由で、正しくない入力をしてしまうケースは当然ありえます。
ですのであらゆるメソッドは、インプットが正しくないあらゆるケースについて考慮されていなければいけません。

function calcWidth(elementId) {
    const element = this.dbConnection.fetchOne(
        `SELECT * FROM elements WHERE elementId = ${elementId}`
    );
    return element.leftMargin + element.bodyWidth + element.rightMargin;
}

たとえばこのコードは、正常系しか正常動作しない悪いコードの例です。
インプットが正しくない可能性を微塵も疑っておらず、かなり品質が低い状態となります。
ですので 《インプットは全て疑う》 の原則に従い、安全を確保するためのコードを追加しなければいけません。

ですがこのときもし、『関数におけるインプットとは、引数のことである』という思い込みがある人が対処したら、ロジックはどうなるでしょうか。
多分こうなるでしょう。

function calcWidth(elementId) {
    elementId = this.toNumber(elementId);
    const element = this.dbConnection.fetchOne(
        `SELECT * FROM elements WHERE elementId = ${elementId}`
    );
    return element.leftMargin + element.bodyWidth + element.rightMargin;
}

先頭行に elementId を toNumber する関数が追加されました。
ここで `this.toNumber()` は、入力値を数値型に変換するものとします。

“引数だけ” をインプットと捉える人は、これで満足してしまうかもしれません。

ですが実際には、メソッドにとってインプットとは、メソッド外にある情報の全てのことをいいます。

  • クラスメンバー

  • 下位メソッド

  • データベースコネクション

  • データベースから返った配列

  • その配列の中に入った値1つ1つ

これらは全て、メソッドからすれば “インプット” ですので、全て正しくない可能性を疑う必要があります。
そして疑った結果、以下のいずれかの行動を必ずとらなければいけません。

  • 誤っていた場合に確実にエラーが出力されるようにする

  • 内部で自動的に正しい状態に是正する

  • 正しくない可能性は低いと判断する

ですので以下のような確認が必要です。

  • 引数 elementId に文字列が渡されるかもしれない

  • もっと言うなら、SQLインジェクション攻撃を受けるかもしれない

  • this.dbConnection が空っぽかもしれない

  • DBへのコネクションが切れてるかもしれない

  • データベースからレコードが返らないかもしれない

  • レコードから返ったカラムが正しくないかもしれない

  • レコードから返ったカラムが文字列形式かもしれない

もちろん確認した結果として、安全とみなして放置するかもしれません。
でもそれは、そもそも確認していないのとは明確に異なります。

3. アウトプットは必ず型を固定する

世の中には、メソッドの戻り値がケースバイケースで異なる状態を「綺麗な状態」と感じる人もいるかもしれません。
たとえば以下は、日付型の引数を受け取って、利用地域に見合った月名を返すメソッドです。

funtion getMonthName(Date date, Locale locale) {
    if ( DateUtil.isValid(date) ) {
        return Error.DATE_INVALID;
    }
    if ( locale == Locale.ja ) {
        return date.getMonth();
    } else {
        return date.getMonthName();
    }
}

仮にこういう仕様だと思ってください。
『日本語の場合は 1 ~ 12 の数値を返す』
『英語の場合は Jan ~ Nov の文字列を返す』
『日付が不正であれば Error 型の値を返す』

とすると、このメソッドはケースバイケースで様々な型を返すことになってしまいます。

このとき、「数値は数値型で、文字列は文字列型で返すのが綺麗だ」だと感じるようなら、これは危険信号です。
数値が必ず数値型であることが綺麗かどうかの議論以前に、『メソッドの戻り値が場合によって違う』ことの方が問題としてよほど大きいからです。

プログラムとは、『同じ仕事を繰り返す』ためのものです。
なのにケースバイケースで戻り値の型が違ったら、そんなの同じ出力を繰り返しているとはいえません。

なので、メソッドの戻り値は常に必ず特定の型のみを返すべきです。
たとえ内部処理の途中でエラーが出てもです。

作ったあなた自身は、このメソッドが様々な型の値を返すことを知っていたとしても、他の人はそんなこと知りません。
メソッドの名前として "Name" という名称を使った以上、これを見た人は『このメソッドは文字列型を返すもの』と思い込みます。

その結果、数値型やError型が戻ったケースでエラーが発生してしまいます。

とりわけ日付型のようなエラーフラグを持たないクラスの場合、異常値だったことを示すために Error や null などを返したくなる気持ちは分かります。
ぶっちゃけ凄い分かります。

気持ちとしては凄くよく分かるんですけど、でもあなたのメソッドを使う人には、そんなことは伝わりません

そしてそれを「知らずに使うのが悪い」と切り捨てるのは簡単ですが、でも意地悪です。
本当に何の生産性もないただの意地悪。

大人数での開発では、このような意地悪はプロジェクトを無駄に引っかきまわし、あなたのチーム内での評価を下げるだけです。

ですので `getMonthName` は、このように実装すべきです。

funtion getMonthName(Date date, Locale locale): string {
    if ( DateUtil.isValid(date) ) {
        return '';
    }
    if ( locale == Locale.ja ) {
        return date.getMonth().toString();
    } else {
        return date.getMonthName();
    }
}

メソッドの戻り値が string であることを明示し、エラーのときも日本語のときも、常に必ず文字列型で返るようにしました。
これで、次の人が誤解なくあなたのメソッドを安心して呼ぶことができます。

4. “業務手順1つ”単位でのテストドリブンを行う

とりわけ、大量の仕事を一気にバサッと押しつけられがちな人ほど、「とにかく急がなきゃ」と考えがちです。

それ自体は当然の心理ではあるのですが、その際に製造(つまりコーディング)までを一気に終わらせて、これを “ひと段落” と捉える人は注意です。
特に急いでるときほどそれをやってしまう人は、テスト段階で品質を落としているケースが多いはずです。
なおかつ、「なぜテストで品質が落ちるのか分からない」と感じてる人も多いのではないでしょうか。

プログラムを一気に作ってしまうと、朝方に作った部分と夕方に作った部分とで仕様に矛盾が発生します。
この矛盾は、そりゃあもう、ほぼ確実に100%発生します。

あなたがもし暗記の達人で、今日1日かけて作ったプログラムコードを1行の漏れもなく完全に覚えているのなら別ですが、1日に数千行ものプログラムを書く人が、その全てを覚えているというのは不自然です。

ですから、

  • 朝方 “バリデーションされた値が入ってくる前提” でメソッドを作った

  • その日の夕方、内部でバリデーションしていないことをすっかり忘れ、

  • そのメソッドを “バリデーションしないで” 呼び出した

こういう系統のミスがどうしても増えます。
ミスを防ぐ手立てを講じていないのですから、当然ミスをします
「自分が作ったんだから自分で分かるっしょ!」と気軽に考える人ほどミスをします。

そうならないためにも、開発にはテストドリブンを使うことが重要です。

本来テストドリブンは “メソッド1つ単位で” テストを行いますが、これだとさすがに時間がかかりすぎてしまうケースもあります。
なので私のおすすめは《業務1つ単位でのテストドリブン》です。

先ほど、ロジックの説明には “何を” “どうする” 構文を使う、という話を書きました。

  • 何を、どうする。

  • そのために、何を、どうする。

  • 次に、何を、どうする。

  • その結果、何が、どうなる。

私の経験では、この説明1行ごとのテストドリブンを行うと、開発速度を落とさずに品質を上げることができます。
(ただし1行1行の説明が、読めばコードが思い浮かぶレベルまで昇華されていることが前提です)

5. コーディング規約を作って・守る

世の中には、「なんとなくあった方がいいと思うから」という曖昧な理由で、感覚的にルールを増やす人もいます。

  • 目上の人に「ご苦労様です」と言うのが失礼な気がしたから禁止する

  • 名刺を黒インクで印刷すると不吉だから禁止する

  • お辞儀は先にやった方が勝ちなので先方よりも早くやること

  • なんか不良に絡まれるっぽいからツーブロック禁止

“気がしたから”“不吉だから”“勝ちなので”“っぽいから”などなど、世の中には感覚的に理由なく作られたルールはあるものです。
そして、そのような感覚的ルールに人一倍多く触れた子供は、ルール遵守の意識が育ちにくくなります
感覚的ルールは無意味に作られているわけで、だから守る意味も特にないからです。

ですのでそのような人は、プログラマーになったときコーディング規約を守る意味が見いだせなくなります。
「ルールは感覚的になんとなくで作るもの」と学習してしまっているために、コーディング規約が全く無意味であるように見えてしまうんです。

当然ですが、違いますよね。

そもそもルールとは、トラブルを防ぐためのものです。
トラブルを防げないルールや、想定されるトラブルがないルールは、存在する意味もありません。

ですから、ルールを増やすときにも、その1つ1つに “守らなかった場合に想定されるトラブル” が存在しなければいけません。
なおかつそのトラブルも「私のときはそうなった」程度ではダメで、トラブルになる確率がそこそこ高いことを証明できなければ、守る側も守る意味を理解できません。

たとえば通常多くの企業では、コーディング規約で1行の文字数の上限が決められています。
これは、“コードが横に長くなって画面からはみ出ると、次の担当者が読み飛ばして不具合の原因になるから” です。

無論、読み手が意識的に気をつければ防げはするのですが、マネージャー歴がそこそこ長い人ほど “全員がそういう意識を持ってるなんてありえない” し、“トラブルになってから指導したのでは遅いこともある” ことをよく知っています。
だからルール化されてるんです

ゆえにルールが完全に守られるためには、“どんなトラブルが想定されているのか” を理解していることが前提となります。

中には「ルールであるからには意味が分からなくても守れ」と指導する先生もいるようですが、こんな指導はただの “逃げ” です。
私はそのような考え方を “奴隷の発想” と呼んでいます。
奴隷は、主人から命じられた仕事を意味も分からずやるものだからです。

だからこそ、コーディング規約に限らず、社会のルールとか、マナーとか、そういったものは

  • リーダーが決めて、みんなで守る

  • みんなで決めて、みんなで守る

ということが大事になります。

とりわけエンジニアは、“顧客の奴隷” になりやすい仕事です。
奴隷化しやすい仕事は他にもいっぱいあるけど、エンジニアはその中の比較的上位にいるんじゃないかと思います。

ルールを決めることそれそのものが、“そんなことになってしまうトラブルを防ぐため” でもあるのです。

2. 大事なのは学ぶことを嫌いにならないこと

よく、訳知り顔で『人生にリセットはない』とか言う人がいますが、別にそんなことはありませんよね。

転職すれば人間関係はリセットできます。
その際、業種換えをすれば人生経験すらリセットできます。

リセットが効かないのって、ぶっちゃけ犯罪歴くらいなものです。

ですがだとするとなおのこと、環境をリセットして最初からやり直していくために、大事なことはなんでしょうか。
それは学び直しを苦に思わないこと。

今の日本の教育制度は、勉強嫌いな子供を量産する仕組みになってしまっています。
ですが日本のこの現状を、政府のせいにするのは現実的ではありません。
なぜなら、たとえ政府の責任だったとしても、それで損をするのはあなただからです。

別に、好きなことだけでいいんです。
好きな物事について学習することを嫌いにならないこと。
それさえできていれば人生のリセットは何回でもできるんです。
無論、自分の都合のいい部分だけ再学習するんでもいいんです。

そうやって周囲の環境を学んでいくことが、『大人数で仕事をする』ってことなんじゃないかなと、私なんかは思うわけです。

ではまた。

この記事が気に入ったらサポートをしてみませんか?