はじめに

私のウェブ開発者としてのキャリアを通じて、多くの異なるWebアプリケーションに取り組んできました。その中で、チームの組織が生産されるコードの品質にどのように影響するか、またそれがアプリケーションの安定性にどのように影響するかを観察することができました。

人的要因の重要性

経験豊富な開発者で構成されたチームが直面する問題は、ジュニアの開発者で構成されたチームの直面する問題とは異なります。

初心者は、ベストプラクティスを無視した構造化されていないコードを生産しがちです。最も適切なツールについての知識が不足しているため、適切でないツールを使用することが多いです。また、経験不足のため、不完全な解決策を好む傾向があります。

経験を積むことで、開発者は自信を得て、時には不必要に複雑な解決策を開発することがあります。たとえば、あまりにも多くの抽象化層を使用したり、適切でないデザインパターンを使ったりする場合です。

最終的に、最も経験豊富な開発者は、時には組織的な問題を引き起こすことがあります。彼らは蓄積された経験によりさまざまな問題を解決するための高度なスキルを持っていますが、これが技術的な解決策を選ぶ際に対立を引き起こすことがあります。

これらの問題はすべて、アプリケーションのコード品質に影響を与える可能性があります。

コードの構造はアプリケーションの品質に影響を与える

アプリケーションでは、「コードの構造」という概念は非常に曖昧であり、いくつかの異なる概念を表すことができます。以下はその例です:

  • ファイルの組織(名前やファイルシステム内での位置)
  • ファイル内でコードがどのように整理されているか(コードオブジェクトの順序、長さ)
  • コードオブジェクトがどのように相互作用またはカプセル化されているか…

このリストは網羅的ではありません。本記事ではファイルの組織に焦点を当てますが、問題とその結果は他のコード組織の側面にも同様に適用されます。実際、これらの各側面はアプリケーションの構造に影響を与えます。

ファイルの組織

ファイルはコードを表し、Rubyでは通常、モジュールやクラスが含まれます。Railsのようなフレームワークを使用すると、基本的なファイル階層が提供されるため、これを簡素化するのに役立ちます。たとえば、設定用のconfigフォルダ、MVCロジック用のmodelsviewscontrollersフォルダなどです。

シンプルなアプリケーションの場合、この階層はファイルを整理するには十分です。しかし、アプリケーションの複雑さが増すと、ファイルも増え、それらはますます大きくなります。

Railsは「concerns」(オブジェクトの側面を表すモジュール)という概念を導入して、機能をモジュールに分割し、それを組み合わせて大きなエンティティを作成します。簡単に言えば、ファイルのコードをいくつかのファイルに分割するわけです。

ここで問題が始まることがよくあります。コードをどのように分割するかを決定する必要があるからです。どのメソッドやマクロをどのファイルに入れるか、どの論理に基づいて分けるかを決める必要があります。また、新しいファイルの名前をどう付けるかも重要です。さらに、複数のconcernsモジュールを積み重ねる代わりに、コードのアーキテクチャを変更して新しい概念やクラスを導入する方が適切ではないかと考えるべきです。

そして、これらの質問には理想的な唯一の答えはありません。どの選択肢にも長所と短所があります。一部の選択肢は重要な影響を与える可能性があり、慎重に検討する必要がありますが、他の選択肢はあまり重要でなく、任意の決定でも十分満足できる場合があります。しかし、どの決定が重要になるかを予測するのは難しいこともあります。

いくつかの構造的な問題とその結果について分析してみましょう。

一部の構造的な問題

最も明らかな問題は、ファイル、クラス、モジュールの名前が不適切であることです。

命名規則に従わないこと(プロジェクトやコミュニティ内で普及している規則と異なる規則を使用すること)。たとえば、Rubyでは、クラスやモジュールの名前はCamelCaseを使用します。この規則に従うことは、Rubyコミュニティが共通して使っているため、コードの読み書きやナビゲートが非常に楽になります。この規則に従わないことは重大な問題ではありませんが、コードベースで作業する際には面倒になります。また、コード品質は何千行ものコードが含まれているアプリケーションでは決して理想的とは言えません。これは簡単に避けられる問題であるにもかかわらず、他の問題を引き起こすことになります。

時には、ファイル、オブジェクト、メソッド、変数の名前を急いで選んでしまうことがあります。これは非常に一般的な問題です。開発中、私たちはほとんどの時間をオブジェクトを作成しているため、それらに名前を付けています。オブジェクトに名前を付けて次の部分に進むのは誘惑的ですが、名前の選択が不適切だと深刻な影響を与えることがあります。コードが読みにくく、理解しにくくなります。名前を誤解すると、大きなバグが発生し、見逃しやすくなります。たとえば、名前が示唆する通りに動作しないオブジェクトは、誤って使用されることがあり、そのバグは必ずしも簡単に見つかるわけではありません。

オブジェクトをいくつかのパーツに分割する場合、問題がすぐに悪化します:

簡単さや怠慢のために、技術的な概念をビジネスの概念として使用することがあります。たとえば、アソシエーションの定義を1つのファイルに、バリデーションを別のファイルに分けることです。コールバックやフィルタなどをすべて抽出することです。一般的には、ビジネスに関連する側面を特定し、そのすべてを1つのファイルにまとめる方が適切です(たとえば、請求用のモジュール、プロセスマネジメント用、住所用、顧客データ用など)。このタイプの問題は、読みにくくなるだけでなく、コードの変更を複雑にします。変更が複数のファイルにまたがることになるからです。

また、不要に抽象化や中間オブジェクトを導入してしまうこともあります。これにより、間接的な処理が発生し、コードの流れを追うために複数のファイルをナビゲートし、迷子になる可能性があります。読みやすさが複雑になり、場合によっては、対応するコードを変更することも難しくなります。

構造的な問題は時に深刻です。たとえば、複雑な技術的ロジックを使用してコードの利用が難しくなることがあります。たとえば、modelsフォルダに加えてservicesoperationsフォルダを作成し、ビジネスロジックをその両方に分けることです。このようなフォルダを作成すること自体は問題ではありませんが、コードベース全体が一貫していなければなりません。そうでないと、コードは読みやすさや変更のしやすさを損なうことになります。悪い習慣がコードに深く根付いている場合、それを整理するのは非常に時間がかかり、難しい作業です。

同様に、ビジネスロジックをヘルパー、ビュー、またはコントローラーに配置することも同じ問題を引き起こします。概念に関するすべてのビジネスロジックを1つの場所にグループ化することは、アプリケーションの保守性を確保するために非常に重要です。

ベストプラクティスを守る重要性

構造的な問題がコード品質に影響を与え、バグを引き起こすことがあることはわかりました。日常的にベストプラクティスを守ることも重要です。

Linterの使用と遵守

ほとんどのベストプラクティスは、Linter(コード解析ツール)を使用することで簡単に遵守できます。

Rubyの場合、RuboCopが推奨されます。個人的には、すべてのポリシーを有効にし、可能な限りデフォルト設定を使用しています。

最初は、特定の制約(メソッドを10行以内にする、行の長さを制限する、モジュールの最大行数など)を守るのが面倒で無意味に思えるかもしれません。これが原因で、開発者は以下のような理由でルールを守らないことを正当化しがちです:

  • コードは十分に読みやすい
  • 他の誰にも迷惑をかけない
  • この場合にはバグがない
  • セキュリティ上の問題はない
  • このルールを守らないことで大きなメリットが得られる
  • など

私の意見では、ルールを一貫して適用することの方が重要であることが多いです。確かに、ルールを守らなくても問題がない場合もありますが、大抵の場合、修正後のコードの方が良くなります。場合によっては、確かにコードが少し悪くなることもあります(たとえば、読みやすさが少し低下するなど)。しかし、この場合でもほとんどのケースではコードはわずかに悪くなりますが、以下の利点によってそのデメリットは補われます:

  • ルールを無視することで、他の開発者がそれを無視したり、Linterのルール全体を無視するようになるかもしれません。これにより、コードが一貫性を欠き、最悪の場合、バグや可読性の問題が発生することになります。これを避けることは確かな利点です。
  • コードは一般的に一貫しており、したがって読みやすくなります。たとえこれが時々コードが若干読みづらくなることを意味していても、それを超えるメリットがあります。

そのため、Linterを使用してそれに従うことは、コード品質に大いに貢献します。

もちろん、特定のルールが十分な利益を提供しないと感じる場合や、個人的な好みによりLinterの設定を変更したい場合もあります。

その他のベストプラクティス

その他のベストプラクティスも重要です。経験豊富な開発者は、初心者よりもそれらをより多く知り、習得しています。

そのため、コードのレビューを経験豊富な開発者に依頼することは、良いコード品質を確保するために重要です。

私にとって最も重要なベストプラクティスは以下の通りです:

  • SRP - 単一責任の原則(Single Responsibility Principle):各コードオブジェクトは、一度に一つの側面に集中すべきです。
  • KISS - Keep It Simple and Stupid:コードはシンプルで愚直に保つべきです。言い換えれば、複雑な解決策よりもシンプルで直接的な解決策が好ましいということです。
  • YAGNI - You Aren’t Gonna Need It:あなたはそれを必要としないでしょう。これはKISSの対義語です。もし今必要でない機能や抽象化(例えば、発生しにくいエラーの処理)があるなら、それは実際に必要になるまで開発する意味はありません。

これらのベストプラクティスは時々互いに矛盾することがあります。その場合は、どれを優先するかを選ぶ必要があります。

ベストプラクティスを守ることはコード品質に大きな影響を与えます:

  • ここでも、コードの読みやすさが向上します。
  • 生産されるコードの量も最小限に抑えられるため、生産性の向上がもたらされます。
  • コードの進化が容易になり、コードに適切なレベルの複雑さが与えられます。
  • コードのシンプルさ、密度、分岐の制限により、メンテナンスが容易になります。
  • 同じ理由で、バグの数も制限されます。

適切なツールの選択

コード品質にとって、良いツールの選択も重要です。選択は、アプリケーションの特定のコンテキストに基づいて行うべきです。あるアプリケーションに適したツールが、別のアプリケーションには適さないことがあります。

ツールを選ぶ際のいくつかの重要な基準は次の通りです:

  • チームの組織と全体的なレベル:あるツールは特定の組織には適しているかもしれませんが、別の組織では逆効果になることもあります。例えば、経験の少ないチームに対しては、シンプルなツール(性能が劣る場合でも)を選ぶ方が、直感的でない複雑なツールよりも適しています。
  • メンテナンスと人気:完璧に適したツールがあっても、それがあまりにマイナーでメンテナンスが不十分な場合、むしろ良くメンテナンスされていて人気のあるツールを選んだ方が良いことが多いです。当然、非常に適したが人気がないツールを選んで、自分で修正したり適応させたりすることは十分可能です。
  • ライセンス:しばしば見過ごされがちですが、ツールのライセンスを確認することは重要な基準です。特に、アプリケーションが人気を集めると、法的な問題を避けるためにライセンスをチェックする必要があります。

多くのショートカットを取らない

複雑な機能を開発する際に一般的な誤りは、KISS原則を過度に適用することです。

コードをシンプルに保つことは確かに重要ですが、機能がどのように使われるかを予測することも同様に重要です。

実際には、開発者は仕様書に基づいて機能を実装しなければなりません。

私が働いた企業では「アジャイル」手法を使用しています。実際には、アイデアを仕様として指定し、それを提供するまでの時間をできるだけ短縮したいという意味です。実際のニーズが表現されたニーズに完全に一致しない場合でも、定期的に見直すことを意味します。

その結果、仕様書を慎重に書いたとしても、しばしば多くのギャップが生じます。個人的には、主なニーズに仕様書の作成努力を集中することがよくあります。これにより、詳細な技術仕様や二次的なユースケースが犠牲になることがよくあります。これらの側面は、開発の他の部分、つまり機能を実装する開発者、レビュー担当の開発者、品質保証チームに委ねられます。

例えば、開発者はエラーケースの処理を見逃すことがあるか、または代替ユースケースを予測できず、完全にニーズに対応しない機能を提供することがあります。

もちろん、詳細な技術仕様を追加すれば、これらの欠点を制限することは可能ですが、コード、ニーズ、使用法、書き込みを分析するために必要な時間が大幅に増加します。ドキュメント作成にかかる時間が増え、仕様でニーズを表現する能力が低下し、結果として開発能力が減少します。そして、すべての側面が考慮される保証もありません。

これを避けるためには、開発者が機能がどのように使われるかというコンテキストを理解することが非常に重要です。アプリケーションの動作に関する正確な知識を持つことで、技術的なニーズを明確にすることができます。例えば、コードに分岐が欠けているかどうかを簡単に見抜くことができ、分岐を作成すべきかどうかを問い直すことができます。また、仕様でエラーケースが処理されていないことに気付くこともできます。

仕様書を文字通りに適用することを避け、挑戦する姿勢を持つことで、バグを防ぎ、アプリケーションの品質向上に貢献できます。さらに、これにより往復作業や開発ステージを避け、最終的にはチームの生産性が向上します。

過剰に設計されたコード

これは前述のケースの対極です。複雑な機能を開発する際に、開発者が過剰に予測して不必要な抽象化を導入することがあります。これらの抽象化により、コードの読みやすさと保守性が低下します。

このようなコードは、レビュー時に検出するのが難しいことがよくあります。

しかし、いくつかのアプローチで対処することができます。例えば、次のような方法です:

レビュー時に、典型的でないアプローチを取っているコードを簡単に識別できます。そのアプローチが複雑である場合、それに挑戦することが重要です。

そのために、白紙の状態から始めて、機能を開発するために使用されるべきアーキテクチャを想像してみてください。

想像された解決策は簡素である可能性が高いです。なぜなら、レビュー担当者はすべての詳細を把握していないからです。その後、選ばれたアーキテクチャと比較します。もし選ばれたアーキテクチャが、レビューされたコードよりも大幅に簡素であれば、追加の複雑さが本当に必要かどうかを開発者と議論することができます。

この種の問題は、アプリケーションの保守や進化に非常に大きな影響を与えます。数ヶ月後に戻って対処するのは非常に難しいことが多いです。したがって、これらを早期に検出し、排除することが極めて重要です。

開発者間の対立

経験を積むと、開発者は習慣を形成します。しかし、開発にはしばしば複数の等価な解決策があります。

そのため、経験豊富な開発者が自分にとってうまくいった解決策を実装したいと考えることはよくあります。

場合によっては、これは開発者が入社する前に行われた選択を見直すことを意味します。時には、新機能に関する問題です。

ほとんどの場合、これは開発者間の議論につながり、時間を消費することがあり、時にはチームの雰囲気に影響を与えることもあります。

これらの議論を軽視しないことが重要です。技術的な選択肢が必ずしも非常に重要でない場合でも、人間的な影響はかなり大きくなる可能性があります。

開発者が理解することが重要なのは、自分にとって劣って見える技術的選択が必ずしも問題でなく、重大な影響を与えることはないということです。

一方で、これらの代替案は時には実際に本当の利益をもたらすことがあり、その潜在的な利益を見過ごすべきではありません。もちろん、それを実装するために必要な労力と照らし合わせて考慮すべきです。もし実装にかかる労力が期待される利益を上回る場合、残念ながら、既存の解決策が劣っていても、それを維持する方がよい場合があります。

しかし、この選択は最終的に機能が再度書き直されたり、アプリケーションやチームが進化したりする時に再検討できます。

一般的に、この種の対立はコード品質には直接的な影響を与えません。しかし、もし対立が解決されなければ、アプリケーション内に複数の競合する解決策が存在することになり、これは明らかに保守上の問題を引き起こします。

結論

簡潔に言うと、コード品質とアプリケーションの安定性は、チームの組織、ベストプラクティスの遵守、技術的選択肢の管理によって直接的に影響を受ける問題です。ファイルの整理やアーキテクチャの決定に関する構造的な問題は、可読性や保守性、最終的にはアプリケーションの品質に重要な影響を与えることがあります。ベストプラクティスを一貫して遵守し、適切なツールを使用し、建設的な議論の文化を作り上げることが、あなたのWebアプリケーションの長期的な成功を確保するために役立ちます。