私のソースコードの書き方

はじめに

ソースコードって実際のところどういうふうに書いていますか?|Rui Ueyama|note を読んで参考になるなーと思ったのですが、はてブ見ても、みんなだいたい同じですみたいなコメントばかりで面白くないので、「上手い人」では無いかもしれませんが、私の書き方をまとめてみました。

ボトムアップアプローチ

私はわりと新規プロジェクトで一からコードを書くことが多いです。人が書いたコードを引き継いで保守した経験はほとんど無いような気がします。

私はトップダウン型で書くのが徹底的に苦手なので、常にボトムアップで書いています。 大規模なプログラムを汎用的に設計するとかは私には無理です。YAGNI大好き。

具体的なコードの書き方ですが、最初はドキュメントに書かれているサンプルコードをコピペして動かします。すんなり動くこともあれば、環境やライブラリのバージョン違いなどで多少手直しが必要なこともあります。

次に、やりたいことに向けて1つずつ機能を追加していきます。ちょっと書いたらコンパイルしてエラーを解消します。で、すかさず動作確認します。自分で書き足すだけではなくて複数のサンプルコードをコピペして、つなぎ合わせる部分を書くこともあります。例えばGoで複数のリモートのワーカーにジョブを実行させるremoteworkersというパッケージを書いたです。ちなみにこのパッケージは実案件では出番がなくて使ってないです。

私は頭の中で全体を把握したり、先に設計してから実装とかが出来ないので、まず書いて動かしてみてから考えます。書くときもなんとなく雰囲気で書いてみることも多いです。で、動かしてみて、あ、こうなるのか、じゃあこう変えよ、という感じで理解を深めながら、コードを書き足していく感じです。

gitでのバージョン管理

コンパイルが通ったところや、ちゃんと動くようになったところでGitでコミットしていますが、ついつい他のこともついでにやっているのでコミットの単位はきちんと分かれていないことが日常茶飯事です。きちんと整理したほうが良いだろうなと思いつつ、新規開発の時はさっさと開発をすすめるほうが優先と思って妥協しています。

実際のところ、新規開発時のコミットを後で見直すことは無いので困ったことは特に無いです。一度リリースした後に保守するときは、修正ごとに極力コミットを分けるようにしています。

私も一直線にゴールに向かうわけではないです。開発初期の時はmasterブランチにどんどんコミットしていますが、横道にそれてなにか試すときはブランチを切って試しています。で結局使わなかった時はmasterに戻って作業を続けて、将来だいぶ立ってから流石にもう使うことはないかと思った時点で試した時のブランチは破棄しています。

ちょっとした変更を試すときはmasterのままコミットして、結局不採用だけど後でみたいかもと思うときはrevertすることもあります。

masterにどんどんコミットというのは1人で開発している時で、複数人で開発するようになったらプルリクエストベースに切り替えています。一方で1人で開発している時でも一連のコミットをまとめておきたいときはプルリクエストを送って自分でマージしています。

テスト

動いている状態を維持しつつ機能追加していきますが、私はテストはめったに書かないです。私は3歩歩けば忘れる鳥頭なのでソースコードも書いた瞬間から忘れていきますが、再度コードを読んで何やっているか理解できればOKです。コード読んだだけでは、どうなるかよくわからないところについてはテストを書きます。

例えばhnakamur/ltsvlogというLTSV形式のログラブラリではログが出力されるかといったテストは一切書いてなくて、いろんな型の値がどう文字列化されるかという箇所だけテストを書いています。

デバッグはログ出力派

デバッガはほとんど使わないです。サードパーティのアプリケーションやライブラリのコードをどこから読んでよいかわからないときに、気になる関数でブレークしてコールスタックを見たりするときとか、デバッグでたまに使う程度です。

自分のプログラムでもサードパーティのプログラムでも、想定外の動きになったり、挙動がよくわからない時はデバッグログ出力のコードを追加してビルドして再度実行して、呼び出し関係や変数の値を確認します。呼び出し関係は、気になるポイントでスタックトレースを出力するデバッグログを追加します。Pythonとかでは例外を投げてすぐキャッチしてスタックトレースを出力したり、Goだとruntime.Stackでスタックトレースを出力します。

今だと拙作のltsvlogパッケージのfunc (l *LTSVLogger) ErrorWithStack(lv ...LV)を一時的に埋め込むという手もあります。この関数はエラーを出力するためのものですけど、あくまで一時的に埋め込んで動作させて、確認したらすぐ消す感じで。

デバッガだとプログラム修正後に再起動してブレークポイント指定してそこまで進んでという手間が面倒ですし、変数の中から見たい情報を見たい形に加工するのも面倒だと感じます。デバッグログなら一回仕込んでビルドすれば後はプログラム実行するだけで毎回必要な情報を出力してくれるので楽です。

リファクタリング

リファクタリングするときに互換レイヤを作るという発想はありませんでした。1つのコミットの変更内容が大きくなり過ぎないように、変更をいくつかのステップに分けて、それぞれコミットしておくようにはしています。

おわりに

ということで正直ベースで書いてみました。

Rui Ueyamaさんのような凄い方と比べるとレベルが低いですが、こんな人もいますよということで。この記事が他の方の話の呼び水になれば嬉しいです。