12周年記念パーティ開催! 2024/5/10(金) 19:00

大規模プロジェクト運用のコツ 前編 コードを書く前に

大規模開発プロジェクトに関わっている筆者が、コードも含めた運用のコツを解説します(不定期連載)。第1回目は、できればコードを書く前に決めておきたいコード運用ポイントについてです。

発行

著者 德田 和規 テクニカルディレクター
大規模プロジェクト運用のコツ シリーズの記事一覧

はじめに

私は現在、大規模なCMS開発に1年半以上携わっています。このシリーズでは、この開発で気づいたことや得た経験を元に、複数人での開発時に役に立ちそうなポイントを、不定期連載でお伝えしていく予定です。

何を使って何を使わないというようなことは、開発前にある程度決めていますが、やはり実際に開発を進めていると、うまくいかないことがたくさん出てきます。

今回は、案件が進んでしばらく時間が経ち、メンバーが替わったり、別の人がメンテナンスを行う必要が出てきたときに、もともと、こうなっていればよかったな……と気づいたことをいくつか紹介します。

案件の規模

はじめに、この案件の規模感をお伝えする必要があります。

現在、弊社ではCMS開発のフロントエンドとUIデザインを担当しています。フロントエンド・エンジニアは現状4人、デザイナーが1人の合計5人で業務にあたっています。

フロントエンド・エンジニアだけで4人の開発です。HTMLコーディングではありません。

全員が主にJavaScript(必要によってはHTML/CSS)を書いています。4人でひとつの案件のJavaScriptを書く、ということでおおよその規模感を掴んでいただけたかと思います。

人数は時々で変動し、少ないときで2人、最大5人のときもありました。

開発に利用しているサービスやメタ言語

次に、開発に利用しているサービスやメタ言語などをいくつかお伝えしておきます。

サービス

フロント、サーバーサイドともにGitHubを利用しています。IssueやWiki、Pull Requestなども利用します。

メタ言語

複数人でコードを書く際にどうしても発生してしまう、書き方の違いをある程度吸収するために、メタ言語を利用しています。

ライブラリ

こちらも違いを吸収するために利用しています。Undercore.jsやBackbone.jsについての解説はしませんので、もし興味のある方は次のシリーズなどを参考にしてみてください。

用語について

Backbone.jsを利用しているので、次の用語が出てきます。こちらも前述したシリーズを参考にしてもらればと思います。

  • Model:情報を保持するオブジェクト
  • Collection:Modelの集合
  • View:表示されるUI

コードを書くときのポイント

開発の規模と、使っているサービスやライブラリをお伝えしました。次にコードを書くときの具体的な注意点を解説しています。

コメントはできるだけ多く、詳しく書く

実装しているものが複雑になればなるほど、その案件特有の問題が出てきます。特有の問題に対処するためのコードは、ソースコードを読めば、その部分で何をしているかはわかりますが、なぜそれを行っているのかまでを読み取れないことがあります。それらに関連している箇所でバグが発生したとして、修正にあたって、もっとも重要なのは、なぜそれを行っているのかという情報です。記述者の意図をくみ取れない場合、そのバグを修正できても、別の箇所でまたバグを発生させてしまうことが多くなってしまうかもしれません。

次のコードは、この案件のコードの一部です。イベントを拾って、その先何をするのかを決めています。

@listenTo(@, 'save:part', _.debounce(@_savePart, 300))
@listenTo(@, 'save:page', @_savePage)

CoffeeScriptをコンパイルすれば、JavaScriptを書いている人なら書いてある処理は理解できると思います。しかし、次のような疑問に答えることは難しいでしょう。

  • なぜsave:partにはdebounce*が必要なのか
  • なぜsave:partのdebounceのwaitが300msecなのか

*注:debounce

debounceは、ある機能が何度か連続して実行された場合に、指定時間内であれば最後に実行されたもののみ実行する、処理を抑制するために機能ですが、詳しくは高津戸の記事を参照してください。

これらがわかるでしょうか。もちろん、書いた本人に聞けばわかることもあります。ですが最初にお伝えしたとおり、1年半以上続いているプロジェクトであるということを踏まえると、書いた本人でさえ、書いたときの意図を正確に思い出せるとは限りません。また、書いた本人がすでにプロジェクトを抜けていることもあり得そうです(このCMS案件ではありませんが)。

そう考えると、書いた本人の記憶もアテにできませんので、実装する各人がコードを書いたその時に、のちのち必要だと思ったことはコメントとして書いておいたほうがよいでしょう。

さいわい、この案件ではCoffeeScriptを使っているので、コンパイル時に、ほとんどのコメントは削除されます*(コンパイル後に残したいコメントは、残せる書き方で記述しています)。つまり、コメントが増えたことによる、ファイルの容量を気にすることはないのです。こういう形にできるというのも、CoffeeScriptを利用する利点でもあります。

*注:CoffeeScriptのコメント

詳しくは『CoffeeScriptで学ぶJSの設計:基本文法その1』の「コメント」の項目を参照してください。

開発初期はどうしても時間に追われていることも多く、コメントを書くということを面倒くさがってしまいがちです。テストを書くことも大切ですが、コメントを書くこともまた大切なことです。

命名規則を決めておく

命名規則を決めておく、というのはとても大切なことなんだとあらためて実感しました。次の例は、どちらもメンテナンス時において、該当箇所をすばやく特定するために必要なことです。

まず、クラス名、インスタンス名、メソッド名の扱いの命名規則です。クラス*名、インスタンス名、メソッド名は検索しやすいように、また、コードの該当箇所だけを読んでも判読しやすくするために、なるべく意味を持った名前にしましょう。

*注:クラス

JavaScriptにはクラスはありませんが、CMS案件ではCoffeeScriptを利用しているため、クラスという概念を利用しています。

例えば、あるViewの中でCollectionを扱うような場合、何に対して実行されているかわかりやすいように、インスタンス名を一目で見てわかるように命名しておいたほうがよいでしょう。

CMS案件では、Backbone.jsとUnderscore.jsを主に利用していますが、これらのライブラリでは、Model、Collection、Viewにはある程度のメソッドが提供されています。Collectionにはgetというメソッドがあるのです。これが、例えば、次のように書かれていると、該当部分だけを見ても、何のCollectionなのかわからないでしょう。

initialize: ->
  @collection = pages
...
sample: ->
  @collection.get()
  # どのコレクションに対するgetなのか、ここだけ見てもわからない

@collection@pagesに変えるだけで、該当箇所だけを読んでも、状況を把握しやすくなりました。

initialize: ->
  @pages = pages
...
sample: ->
  @pages.get()
  # pagesに対するgetということが一目でわかる

次に、イベント名の扱いです。例えば、DOMを通してのイベントであれば、そのViewの中で完結していることも多く、あまり気にすることもありませんが、オブジェクトを通してのイベントを扱う場合は、命名規則を決めておくとよいでしょう。

例えば、何らかのDOMのクリックイベントを監視していて、それをController(処理をまとめるところ)へ渡す場合に、イベント名をclickにしていたとします。

# view
_onClick: ->
  @trigger('click', @)

# controller
_eventify: ->
  @listenTo(@someView, 'click', ->
    # 何らかの処理
  )

clickという名前は、そもそもDOMのイベントで定義されていますから、よく使われます。こういったように、イベントを持ち回しているような箇所が多数あった場合、書いた本人以外の人(もしくは1年後の自分)が見たとき、これは結局どこでどうなってるんだろう、というのが少しわかりにくくなってしまいます。

イベント名で検索しようにも、clickはたくさんあります。結局トレースしていくことになるのですが、少し時間がかかってしまいます。はじめからこのイベント名が固有のものになっていれば、そのイベント名を検索すればすぐに関連箇所がわかります。ですから、イベント名は識別子で扱うとよさそうです。

では具体的にどのような命名規則を設けたらよいか、一例を挙げます。CMS案件では、イベントに次のような命名規則を設けました。

イベントの命名規則

  • それが何のイベントなのかが一目でわかるように命名する
  • 動詞は過去形などを使わず常に現在形
  • 各部名称が長くなる場合は、スネークケースを使いキャメルケースは使わない

イベント名の形式の一般化して書くと、次のようになります。

「結果どうなったか、あるいはイベントハンドラ」「何を行ったか」「どのタイミングで行ったか」「イベント発生元の名前」を:で区切って書きます(該当するものがない場合は書きません)。

([handler or event])(:[doing])(:[timing])(:[namespace])

イベント名は例えば、次のように書くことができるでしょう。

# [event:doing:namespace]
success:publish:sitemap # サイトマップの公開が成功した
# [event:namespace]
revert:page # ページの変更を元に戻した

次の例は、それぞれ問題点を修正する前と、後のものです。

# すべてが繋がっていて、理解しづらい
beforeselectpage
# 区切ることで理解しやすくなった
select:before:page

こちらは、ルールに沿っていないものを、ルールに合わせて書き直しています。

 # 過去形を使っている
initialized:newpage
# ルールに合わせ過去形を現在形にした
# newpageは読みやすいようにスネークケースにした
initialize:new_page

規則の運用方法

CMS案件では、ここで紹介したようなエンジニア同士の取り決めは、GitHubのWikiに書いています。何らかの規則を作るべきだと気づいた時点で他のメンバーに相談して、Wikiに起こしてレビューしてもらい、合意が取れた時点からルールとして運用しています。

案件が本格的に始まる前からこういったことを決めるのは、規模にもよりますし、難しいことですが、メンバー全員が参加して決めておくべきでしょう。途中で気づいたとしても、面倒くさがらずに、気づいた時点で伝えて合意を取っておくべきです。時間が経てば経つほど、開発は進んでしまい、問題の根は深くなっていきます。

まとめ

開発が大きな規模になればなるほど、その時点の実装よりも、のちのちの仕様変更やバグに対する対応に費やす時間が多いものです。開発を続けていく以上、今だけのことではなく、これから先のことを考えましょう。1年後の自分は他人と考えて(おくべきとはよく言われることですが)、メンテナンスしやすいコードを心がけるべきでしょう。