Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moriyoshi/8b233b0848e04bc4e907c61bad735281 to your computer and use it in GitHub Desktop.
Save moriyoshi/8b233b0848e04bc4e907c61bad735281 to your computer and use it in GitHub Desktop.

この記事はpyspa advent calendar 2020の18日目の記事です。前日は、flagboyの配られたカードで勝負するでした。主旨は異なりますが、徒然草百十段の「双六の名人」が思い出されました。明日はYutaka Matsubara a.k.a. mopemopeさんの記事となります。お楽しみに。

1ヶ月で10,000行くらいのコードを書いた話

行数を書くと「いやいや、私はもっと高い生産性を持っている」、という方は少なくないだろう。登大遊氏に至っては、ネットワークプログラミングという複雑な領域での実装でありながら「1日に少なくとも3,000行程度、多く書くときで10,000行」と述べており、凡才さを思い知らされる。

さて、この記事は、私のような動物でも高効率にコードを書くために何をしたのかを備忘として残しておく主旨である。

開発環境編

書いたコードは複数プロジェクトに渡るのだが、その中で最も分量が多かった (8割) のは (JSONAPI-Serde) であるので、このプロジェクトについて説明する。

このプロジェクトはPython用のJSON:APIの呼び出しにおけるペイロードのシリアライザ/デシリアライザであり、データ型の特性やWebアプリケーションフレームワークに依存せずインスタンスとペイロードとを対応付けることができるのが特徴である。 既存のJSONAPIを扱うライブラリは品質が低く、案件の進行に支障が出たため、スクラッチで書いて、置き換えることを意図した。

BlackやFlake8といったフォーマッターやリンターを導入する。

告白すると、実はもともとフォーマッターやリンターには否定的な立場だった。というのも、ソースコードを書くよりも読むほうに圧倒的に時間をかけてきた身からすると、ソースコードのフォーマットが統一されていないことに対する認知的負荷は相対的に高くなかったからだ。各々が一番書きやすい書き方でコードを書くのが最も生産性が高いものと思っていた。しかし、少なくない同僚がその点を気にしてきたことを知り、無慈悲にフォーマットが統一されるほうが結果的に生産性が高まると判断するに至った。

Pythonのフォーマッターやリンターには見出しに挙げたBlackやFlake8の他に、Pylintやautopep8やyapfなどの製品が存在するが、主観的に考えて最も合理的な整形を行うものがBlackであり、入力フォーマットに関わらず柔軟なルール設定が可能なものがFlake8だったので、これらを採用した。

型アノテーションを活用する

Pythonは多くのスクリプト言語同様いわゆる動的型付け言語であるが、これは大規模コードをメンテナンスする上で障壁になることが多い。コードの規模が大きくなればなるほど、コードを読み返す機会が増えるものだが、同時に型を演繹するための文脈を前知識として持っておくことが難しくなるというジレンマがつきまとうためである。いくつかの動的型付け言語では、こうした問題に対処するために、非侵食的に静的型付けのセマンティクスを導入することを試みてきた。

静的型付けのないプログラミングというのは、クライミングでいえば、ロープを固定するアンカーなしで岩に登るフリークライミングを熟練度を問わず強いるようなものである。しかも、もっと悪いことに、現実に存在する岩と比べてソースコードの方がはるかにトポロジー的に複雑な形状をしているのだ。同じアナロジーに固執するとすれば、ソフトウェアプロジェクトというものは、いかに華麗に速く登るかではなく、全員が安全に登って降りられるかが重要であるので、しつこいくらいアンカーを打っていく必要がある。

Pythonのアノテーション機能を用いて静的型付けを実現するソフトウェアは以下のようなものがある。

このうち、最も手軽に導入が可能なmypyを採用した。最も手軽に、というのは、型チェッカー自体がPythonで記述されており、外部の依存が少ない点を評価している。また、型チェッカーの機能を補うための拡張機能が数多く用意されていることも有力な選択理由であった。

CIを導入する

以上の準備が整ったら、バリデーションプロセスが自動的にVCSのアクション (gitでいうとcommitだとかpush) に応じて実行されるようにする。こと少人数の開発では、担当する作業のバリエーションが増えてくるとコンテキストスイッチによる消耗が大きくなり立ち位置を見失うことがしばしばあるが、そうした乱れに対して開発のリズムを形成するのに欠かせないのがCIであると考えている。

ローカルでのソースコードのチェックにはpre-commitを、レポジトリでのチェックにはGitHub Actionsを利用した。

ライフハック編

要件が複雑で終わりが見えないプロジェクトを抱えつつ、納期がある中で一定のスループットを出すためには、それなりの心構えが必要となってくる。私の場合は、以下のようなポイントに留意しながら、およそ3カ月に渡って作業を進めた。

自分がもっとも集中できるタイミングを見つけ、そこに全振りする

私の場合、平日の日中は何らかのコミュニケーションが必ず発生するため、集中して作業できるのは次の時間帯に限られる。

  • 平日: 20:00〜翌8:00
  • 休祝日

この時間帯に集中して行う必要のある作業を実施するため、睡眠のタイミングを調整するなどした。

2時間以上は寝る

人間が眠らなければならない理由は未だ解明されていないが、何らかの細胞間伝達物質や受容体が関与していることは確かであり、思考を阻害する物質を取り除くため (あるいは正常な思考を保つための物質を産生するため) には、アーキテクチャ上睡眠が必要である。今のように締切が近い時はなんと不都合な仕様だろうかと思うわけだが、DRAMがメモリセルに対して定期的にリフレッシュを行わなければならないのと同じようにハードウェアの制約であるので、努力でなんとかできるものではない。

そのため、小分けでも何でもこの「リフレッシュ」を行うタイミングを用意しなければ、恒常的にスループットを保つことはできないので、とにかく打ち合わせの合間でもなんでも時間を見つけてこまめに寝ることで、全体として一日最低2時間は寝るようにした。

プロジェクトの進行に合わせて要求が増えてくると、不安が増大し、なかなか寝付けないという状況にも陥った。この状況への対処は私の場合三つあって、

  1. 眠れなくてもとりあえず横になる
  2. 眠れないことを予期して事前にジムなどで運動しておく
  3. 本当に眠くなるまで寝ないで作業する

のうち、状況に応じて最も適合するものを選んでいった。

こまめにゴールをつくり、開発のリズムをつくる

人間が最も高い効率を発揮できるのは、適切な報酬が適切なタイミングで与え続けられるときである。この報酬というのは金銭的なものではなく、いわゆる報酬系を刺激し、神経伝達物質であるドーパミンの放出を促すようなものごと全般を指している。言い換えると、コストのかかる行動について、相応の報酬があるということを脳に認識させる (誤解させる) ことが重要になる。

個人差はあるが、利他的な性質が少しでもあれば、複数人で開発していると、おのずと周囲とのやりとりの中で細かなタスクが生まれ、それらを消化していくだけでおのずと報酬系が活性化していくものである。しかし、一人で開発を行うとなると、特に未来に得られるであろう報酬が見えない中では、サンクコストを最小化するため常に「逃避」を選択肢に置いてしまうことになるため、効率は無意識に低下してしまう。

こうした状況を改善する方針は二つあり、

  1. 作業自体のコストを過大評価しないようにする
  2. 作業から得られる報酬を過小評価しないようにする

である。

前者については、まず、タスクの性質をよく見極めることである。乱暴にいうと、タスクには時間あたりのスループットが一定であれば線形に達成度を上げられる種類のものと、達成度を高めるコストがその時点の達成度に対して指数関数的に増大していくものとがある。

この違いを言い換えると、前者は8割の達成度に到達するまで10時間かかったとしたら、10割に到達するまで12.5時間であるが、後者は8割まで10時間かかったとしたら10割に到達するのには20時間かかる、といった状況となる。

後者の性質を持つものを前者の感覚で進めてしまうと、いつまでも作業に見合った報酬が得られないことになり、最も避けなければならない悲劇が生まれる。早い段階で後者であることを認識し、そのような認識ができた時点で完全なアウトプットを放棄するべきである。これは完璧を目指してはいけないということではなく、報酬系の仕様上、認識したゴールまでの所要時間をできる限り短縮することが重要になってくるため、完成度を上げる作業はさらに作業を分割する工程を挟んで別の工程とすることが、取るべき戦略として最も賢明だということである。

次に、長くても2時間くらいで達成できるゴールをこまめに設定すること。作業の見積もりができないのであれば、もっとも手前にあるゴールを設定する。具体的には、ユニットテストをテストファーストで記述する、などが挙げられる。テストファーストといっても本当にテストを実装より先に書く必要はなく、ファーストランまでにテストを書いて作業の輪郭を決めることで、正しく達成感が得られるように手配する。

後者については、複数の作業から得られる報酬の一貫性が問われることになるので、定量的に定義できる報酬を何でも良いので自分の中で決める。狭義のゲーミフィケーションともいえる。ふさわしいのは、タイトルにあるように行数 (ステップ数) や、コードカバレッジなどである。個人的にはユニットテストとの相乗効果を考えて、カバレッジ可視化ツールの導入と、コードカバレッジの計測をおすすめする。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment