勢いよく個人開発を進める時のトレードオフを許容してみる

最近、マストドンっぽいモノを自作してみている。ActivityPub でやり取りが出来て、他の Mastodon 鯖からユーザを認識してフォローしたりいいねが出来たりする、Mastodon と互換性のあるアプリケーションだ。

コレについてはまだまだ作成途中だが、「趣味の個人開発」として取り組んでいて気付いたことがあったので、それを書く。


フロントエンドは Angular、バックエンドは Misskey と同じ NestJS を使っていて、コレを書いている時点での LoC (コード行数) は次のような感じ (cloc コマンドで計上)。

$ cloc ./backend/src/
      29 text files.
      29 unique files.
       0 files ignored.

github.com/AlDanial/cloc v 1.82  T=0.01 s (2384.3 files/s, 99893.7 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
TypeScript                      29            115            157            943
-------------------------------------------------------------------------------
SUM:                            29            115            157            943
-------------------------------------------------------------------------------

フロントエンドもコード行数は大体同じ。

$ cloc ./frontend/src/
      50 text files.
      49 unique files.
       6 files ignored.

github.com/AlDanial/cloc v 1.82  T=0.02 s (3220.5 files/s, 70522.9 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
TypeScript                      28             92             85            579
CSS                              8             21              1            162
HTML                            13              8              0            125
-------------------------------------------------------------------------------
SUM:                            49            121             86            866
-------------------------------------------------------------------------------

実コードの行数としてはフロントエンド866行、バックエンド943行と、ともに1,000行いかないくらいの物量なのだが、ココまでの実装は中々大変だったなぁと感じている。

大変だなぁと感じた理由の半分以上は、自分が32歳で、鬱病持ちで、ココ最近はバリバリプログラミングをするような生活ではなかったからだとは思うのだが、それを加味しても、個人開発をガンガン進めようと思った時に引っかかったポイントがあったと思う。


僕は元々コメントをよく書く方で、特にドキュメンテーションコメント (TSDoc・JSDoc の類) は欠かさずに書く方だ。だが今回、どうもコメント行が多いと気が散る感じがして、最終的にはコメントをガンガン削った。

特に、ドキュメンテーションの @param とか @return とかの説明を書かないことにした。今回は TypeScript で実装しているので、型宣言で説明が付いていればドキュメンテーションコメントは書かなくても済むはずだと考えたのだ。

それでも、例外をスローする箇所に関してだけはコメントを書いて明示的にすることにした。Java でいう throws キーワードが TypeScript にはないので、「この関数が例外を吐き得る」という行には // Throws と書くことにした。コレだけでコメントは十分な気がした。

こうしたコメントを省いても問題なかったのは、コレが個人開発プロジェクトだからだ。他人がコレを読む時はコメントが足りなくて分かりにくいと思うかもしれないし、今まさにガンガン開発している最中の自分にとってコメント行が邪魔なだけで、数年経った時にコメントなしで構成が把握できるかというと怪しい気がする。

プラクティスの一つとして、「メソッドを小さく作って、命名で示して、中身は忘れる」とよく言われる。それ自体はそうだと思うのだが、個人開発をガンガン進めている最中は、どうしてもメソッドの中身まで理解しておかないと実装が進められないと思うことが多かった。特に、単純な CRUD 処理ではない部分、外部からのイベントに応じて複数の処理を行わないといけないような部分で、どの関数が何をしているのか、忘れてしまうとどうにもならない、と思うことがあった。

コレについても、コメントを省きまくることで、逆にコードだけで話が読めるようになって、良い効果を得られた。


Angular と NestJS はモジュール単位で分離がしやすい仕組みを持っているが、今回はモジュール分割を止めた。モジュール同士の循環参照エラーとかで時間を取られていると、本質的な開発に頭のリソースを回せない感じがした。だから、全てをグローバルな AppModule にブチ込むことにした。

import の順番とかも意味で整理しようとするとキツいので、アルファベット順で自動的に並べることにした。ファイルツリーはどうせ名前順で並ぶので、それと同じ順番で import が書かれている方が見付けやすいなと思った。


Angular と NestJS は、コンポーネントないしはコントローラから Service クラスを DI して使用する。大抵は複数の Service クラスを利用するので、コンストラクタ部分の記述がこんな感じになりがちだ。↓

class HogeController {
  constructor(
    // コレが5・6個とか並んでしまうことはよくある
    private authService: AuthService,
    private postsService: PostsService
  ) { }
}

ココで、自分は今まで配列などにおける末尾カンマを付けない派だったのだが、個人開発をガンガン進めていきたいと思った時に、この末尾カンマがある方が楽だということに気付いた。

例えば上述のコードの状態で、新たに UsersService を追加したいと思った時に、

  1. PostsService の後ろにカンマを付けて、
  2. UsersService を次の行に書いて…

という2つの手順が発生してしまうところ、PostsService のところに予め「末尾カンマ」を付けておけば、いきなり UsersService を追加できる。地味なことだが、コレだけで作業のスムーズさが段違いな気がして、今回のプロジェクトでは末尾カンマを付けることにした。

コレについても、末尾カンマ自体が可読性を上げるかというと、僕はそうは思わない。カンマがあることで、「まだ次に何か要素が出てくるのかな?」と思ってしまうし、引数の数を勘違いしたりしやすいと思うからだ。あくまで開発者が開発を進める時に、変更を入れやすくするための処置でしかないと思っている。


Service クラスを作るとなると、その置き場に困る。単一責務の原則を守って実装したいから、どの階層に置いて、どういう名前にしようか、と悩みがちだが、今回みたいに ActivityPub の仕様も分かっていないまま実装を何となく進めようという時には、そうしたキチッと考えられた、設計されたモノ、というのはどうせ作れない

だから、実装中はある程度汚いコードを書くことを許容することにした。コントローラ内にビジネスロジックを書いても良い。バリデーションをやっている場所がイマイチでもとりあえず今は良い。前後処理がイマイチなところに TODO コメントが残りっぱなしでもいい。とにかく本質的な機能を実現するところに集中して実装する。そしてリファクタリングは後で、リファクタリングだけを行うことにした。

リファクタリングをすることで、実装した内容の整理が付くし、イマイチポイントや次に実装すべき箇所も分かってくる。リファクタリング相当の事柄を、実装中には考えないようにするっていうのが、とにかく機能を実現して開発を進めるには有用だと思った次第だ。

なお、リファクタリングの際にも、ドキュメンテーションコメントを書き足すことは今回していない。今回は最後までドキュメンテーションコメントを書かないで行ってみる。


さて、ココまで話してきたことを整理すると、

ということで、大変お行儀が悪く、今までの自分だったら絶対にやろうとは思わないムーブだ。

しかし今回は、

という状態で、かつ、他に誰も関わらない個人開発である、というところで、色々と無計画に、考えなしに動かしているところが大きい。

言ってしまえば当たり前のことなのだが、何も分かっていない時に、最初から完璧なモノを作り出そうというのは無理な話で、だから今回はあえて汚い状態のモノ、独善的なコードを書くことを自分に許可してみたのだ。そうすると、今々はとりあえず開発を進められるようになった、というワケだ。

キレイに書こうとすること、可読性を保つこと、後で構成が分かりやすいようにしておくことを諦めたワケではない。だが、ある程度犠牲にはした。そしてそれを定期的なリファクタリングで最低ラインに保つ、というやり方にした。

多分、2・3年後のスッカリ忘れた自分が読み解く時のことを考えると、ドキュメンテーションコメントはあるべきだし、意味がもう少し読み取れるコードであってほしいし、末尾カンマなんて許せないし、なんで自分はこんな汚いコードを書いたんだとは思いたくないのだが、「とりあえず前に進める」ことを考えた時に、こういうやり方もアリなんだな、とちょっと思った。


コレって、アジャイル開発ってことなのかな。でもチーム開発ではやっぱりワケが違ってくると思うんだよな。

「楽しそうなところから好き勝手に開発していい」もんじゃなくてビジネスが先行するワケだし、メンバと分担を決めるにはモジュール分割が出来たりしないと話がしづらいし、一人で好きに作ってバグらせようが誰にも迷惑掛けないけど、チームで作っててバグを生むのは迷惑であり遅延の要因だし。それでいいんです、とはやっぱり思えないな。一人の趣味プロジェクトだから許されるやり方であって、やっぱりチームプレイやビジネスのこと考えると、自分が今やっているムーブは大変気持ち悪くて受け入れづらいモノがある。

今のところ、この辺の塩梅をうまく調整しているアジャイル案件に参画できたことがなくて、スクラムマスターのヤツには恨みしかないから、やっぱり仕事とは別。

ただ、僕は仕事で書くコードも個人で作るコードも同じように考えていたところがあったが、「汚くて独善的なコードを書く」ということが、個人開発でならアリかもしれない、というところが今回の発見だった。