「ソースコードに間違いが見つからないのに想定される出力をしない。あるいはソースコードに修正を加えていないのにいきなり想定出力を返すようになった。」
こういう経験がある人はいるはずだ。なぜこれが起こるのか。一つの原因を見つけた。
それは環境変数や設定ファイルに存在する。デプロイ時には設定ファイルを特定の値に修正してから、ということがあるだろう。
開発環境でコーディングする人が、デプロイ時の設定ファイルには関与せず、デプロイの担当者がそれを把握している。
開発者はセキュリティ上の理由でデプロイ時の設定ファイルの内容を見ることができない。
この場合、設定ファイルの内容が間違っていても、開発者が原因が正しく特定できないケースがあるのである。
対処方法は以下である。まず事前にやっているであろう対処は以下である。
追記:
プロットライブラリ、データフレームライブラリ、データベース、などが集計関数を用意している。
例えばある場所ではプロットライブラリの集計を使っているが、別の場所ではデータベースで集計してからプロットするということがあるだろう。
各ライブラリが内部でどういう処理をしているかがブラックボックスであるため、これは問題である。
ライブラリの集計関数を使う場合、テスト用のデータを用意しておき、集計値が一致するかを確認するのがまず必要。
次に集計方法はバラバラでなく揃える必要がある。プロットライブラリに集計させるより、データフレームに集計させてそれをプロットしたほうが良い。
またデータフレームにおいても、groupbyとpivot_tableで集計の扱いに差があったりする。
これらの差が生じる一つの理由はNullやdatetimeに対する処理の違いだったりする。
暗黙の集計に対応するのは大変なので、テストデータに対する集計が正しいバリエーションを選び、その方法で全部揃えたほうが良い。
アジャイルがどうの、ドメイン駆動開発がどうの、マイクロサービスがどうのと、開発プロセスについての情報が巷にあふれている。
しかし勘違いしないほうがいい。あなたの現場にとって最適な方法を追求できるのは、あなたの現場の人間だけだ。
外の世界の「これがうまく行った」論は、文脈を無視しては話にならない。企業Aの文脈と企業Bの文脈が全く別のものであるなら、開発プロセスの成功法則に再現性がないのである。
「開発でこういうことが困っている」ということがあれば、それを列挙するところから始めるべきだ。現場の人間は「問題」がはっきりすれば解決策を考え出すだろう。
「モジュールの独立性について困っている」という話をしているときに、「マイクロサービスとして独立させよう」という情報がググって出てきたら疑ったほうがいい。
無料のAPIをサービスとして公開しているとして、それに依存するコードを書いたとする
例えば翻訳APIを無料で公開していたとして、あとから有料になるということだ
有料になってコストがかかると、そのAPIへの依存度が高ければ高いほど、ビジネスとしての損失につながる
プロプライエタリAPIに依存しそうになったときは、それを自前で実装できないかまず考えろ
例えば事前訓練済みモデルであればhuggingfaceが使える場合があるだろう
損益が利益を大幅に上回る場合に、huggingfaceでモデルが見つからなかったり、代替策がない場合は、関係各位に相談し「機械翻訳を使うことをやめる」ことを検討したほうが良い
追記: jparacrawlは商用利用が不可らしい
実装を進めて、あとの方で「やっぱこうして」といって後戻りの工程が発生すると穴を掘って埋めるような感覚になる。
こういうのは効率性を低下させるので、関係各位と調整して情報を揃えたほうがいい。
もし情報が限定的にしか揃わない場合は、非常に簡易的なプロトタイプを触ってもらって、「こうしたほうがいい」という声をもらう。
情報として基本的なのは、UI設計とデータ設計だろう。これらが煮詰まった段階でデータフローのシーケンスを特定していく。
例えばUIの担当者が外観の設計を渋っているときは、入出力だけでも特定し、その入出力のプロトタイプを作っておいたほうが良い。
分析ツールを作って、様々な凝った統計情報を表示したいと思ったことはないだろうか。
ロジスティック回帰でモデリングして係数表示をしたり、決定木を視覚化したり、相関の行列をヒートマップで表示したりと、いろいろなことができる。
しかしいざツールを作ってみると、「そんな分析は必要ない」と叱責されてしまうのである。これは一体どういうことなのか。
それは開発に近い人の考える「分析」とビジネスに近いところにいる人の「分析」が、メンタルモデルからして全然違うのである。
ドメインに近いところにいる人たちは、もっと基本的な統計を要求するだろう。
収益の推移だったり、アイテムが特定の属性のユーザーにクリックされる確率だったり、特定の条件に合致するアイテムの単価の分布だったりと、そういうものだ。
開発者がやるべきことは、csvファイルをアイテムに対する特定の検索条件・グルーピング条件などで出力してダウンロードさせることだ。
開発者AとBがいる。
開発者A「ビジネスモデル全体を最適化するための施策を実施中です」
開発者B「アイテムの重複を避けるために、アイテムの属性の文字列から固有の識別子を生成しています」
一見すると開発者Aのほうが全体を俯瞰していてデキそうに見える。開発者Bが無能のアスペのようだ。
しかし開発の進捗を確認すると、Aは全く進んでおらず、Bは「重複を排除するロジックが完了したので、これを効率的に実行できるようにしています」と言っている。
ご察しの通り、問題を細かく分解していかないと、開発というのは進まない。
全体を俯瞰してビジネスについて考えているふりをするだけではコードという形にはならないのである。
開発者Bは穴を掘り進めなければならないので、実際に掘っている。開発者Aは穴を掘る必要があるかどうかすらわかっていない。
別の言い方をすれば、巨大に見える問題も、適切に分解すればグイグイと進んでいくとも言える。開発者Aのように巨大なままで捉えていると、何も実装できない。
Vimを使っている開発者が、pythonコードのインデントをスペース2として書いていた
他の開発者はpep8に従っているのでインデントはスペース4である
Emacsでは、tabを押せば即座にスペース4として補完されるのでタイプ数が増えるということはない
ところがこのVim利用者はスペースを2連打して入力していたようである
コーディングスタイルは、原則としてグローバルスタンダードとなっているものを採用した方が良い
pythonであればpep8を使えば、他のコードとの整合性もとれる
もし他の開発者が「スペース2のほうが生産性が高い」というなら、tab一回の入力で補完されるような環境設定を推奨すべきである
つまり、コードブロックを視認するためには4ぐらいの幅があったほうが見やすいということだ
工程には段階がある。
アイデア → 要件定義 → 設計 → 実装・テスト → 運用
という流れがあるなら、「アイデア」の段階での試行錯誤が一番コストが低い。
「運用」の段階で「やっぱりこのサービスは儲からないからやめよう」となると、それまでかけたコストが水の泡になる。
つまり前の工程ほど、試行錯誤をするコストが低いと言っていい。
一方、「サンクコストバイアス」には十分注意するべきだろう。
「アイデア」の段階で、誰かが特定のアイデアをお気に入りのアイデアとして採用し、それを深めて議論していたとする。
そうして、別の誰かがさまざまな検証を行った末に、そのアイデアで成功する確率が低いと分かったとしよう。
アイデアの段階の良さは、試行錯誤コストの低さであるため、ダメだと分かったアイデアはすぐに捨てるようなつもりで挑んだ方が良い。
そうでなければ、ダラダラと運用までたどり着いてしまい、何にも儲からないサービスを運用することになるだろう。
運用までたどり着くと、サンクコストバイアスはより強固になり、コストにしかならないサービスを意地で運用しようとしがちである。
ベンダーロックインとは、特定のベンダーの製品を使うことにより、その仕様に合致した周辺環境やコードを設定してしまい、移行が困難になるような現象だ
最近、BigQueryを使うことによってこのベンダーロックインにぶち当たった
「使うにはコストと制限があるから、やっぱ自鯖にしよう」となったわけである
BigQuery特有の機能を別の環境に移行するには大幅な変更が必要になる
ベンダーロックインの臭いを嗅ぎ取ったら早めに判断し、避けた方が良い
もし後から「やっぱこれ使いたくない」と言ってすでに依存状態にあるシステムから移行しなければならない場合は、
BigQueryであれば何らかのNoSQLを使うか、スキーマを無理やり抽出してmysql等に変換する方法もあるだろう
そのようなことを自動的に行う有料のサービスも存在するかもしれないが、新たなベンダーロックインとならないよう、注意深く仕様を見た方が良い
「この関数にこういうパラメータを使ったこういう処理を追加してくれ」などと言われたら、コードは複雑化するのは当然だろう。
かといってこういう要求が来た時に、コード全体を一から作り直して簡潔にしようと思うのはナンセンスだ。
コードの量にもよるが、一定程度の量のコードがそこにあるときは、やはりリファクタリングの方が効率よく進められる。
「僕はリファクタリングなんてしませぇん、一から書いた方がいいでぇす」というのは、特定の現場・状況だけにあてはまるものだと認識しておこう。
確かに「コード全体をリファクタリング」なんてしようと思ったら大変すぎるが、通常は「修正を担当する部分をついでにリファクタリングする」でOKだ。
ユニットテストさえかけていれば、そのリファクタリングによって、バグが見つかりやすくなるだろうし、保守性も上がるのである。
なお、本当にコードベースが酷いカオス状態で、ゴッドオブジェクトを使っているような状況になったら、「書き直す」という利点が少しはあるかもしれないが、そういう場合は関係各位に同意を取らなければやってはいけない。
そういったカオスな状況でさえ、平均的なプログラマーは「良いコード」よりも「慣れているコード」に愛着を持つ傾向にある。
もしあなたが「コードを綺麗にするためにすべてを一から書き直そう」と、無断でそのようなことをやったら、彼らが慣れていないという理由で批判の嵐が殺到するだろう。
コードを簡潔に保つにはモジュール化が必須である。しかし同じモジュールに関係のない機能が含まれていたりすると混乱の元になる。
一方で、関数というのは引数の細かな仕様に依存せずに、汎用的に呼び出せた方が何かと好都合だ。引数になんらかのオブジェクトを渡し、そのオブジェクトしか持ち得ないような特殊な情報で処理を行なったりすると、関数とオブジェクトが互いに依存しあってしまう。
これはモジュールの結合度と呼ぶ。
高い凝集度、低い結合度によってモジュールを作れば、保守性は上がる。
さらにモジュール内では、公開する必要のない関数はprotectedまたはprivateにするべきだ。
そのためにはモジュールが公開すべき関数についてインターフェイスを作り、公開関数に対するユニットテストを書いておくのが良いだろう。
pythonコードの速度のボトルネックを見つけるにはline_profilerが使える。
だが一部の開発者は「速度に凝り過ぎるとコードが読みづらい」という。
これには異論がある。
大幅に速度を改善するようなコードの改善は、むしろコードをシンプルに保つ上でも重要な働きがある。
傾向としては、マルチプロセッシングなどを使わずに速度を改善した場合は、プログラムの長さは減少する。
速度を改善すれば、特定の出力をするコードの最小長(コルモゴロフ複雑性)に近づく。
速度改善によってわかりにくくなるという人は、数学ができないのかもしれない。
物理学では、変数を単一の文字で表すことが多いが、こういうのに慣れていると「シンプル」の概念に差が開く。
こういった科学的な「シンプルさ」を理解できない人に対して、意味を説明する形で変数名を決めても、結局コードは理解できないだろう。
確かに、ビジネスドメインに近いコードであれば変数名をドメイン語に合わせるのがわかりやすい。
しかし「ボトルネックを改善しなければシステムが要件通りの速度にならない」ようなケースでは、数学的なコードの方がわかりやすくなるのである。
特定のインデックスを管理する技術者(Aさん)がいるとしよう。
このインデックスがサイト全体の検索で使われるため、CTRを気にするなら最も重要なパーツだ。
ここで私が検索アルゴリズムを改善するための特徴量をインデックスのフィールドに挿入したいというとする。
しかし特徴量を管理するのが私であるのに対し、インデックスを管理するのがAさんであるので、どうやってこの受け渡しをするのかという問題が生じる。
私がインデックスのマッピングルールを書き換えるわけにはいかないのである。
そこでバッチ計算してそれぞれのアイテムIDに対する特徴量をDBやプレーンテキストに保存しておくようにする。
すると
という流れが生まれる。
phpの場合、<?php 処理 という具合に書くが、この中身にはhtmlやjavascriptも包含することができてしまう
MVCフレームワークを使わないにしろ、基本的にビューとバックエンド処理は分割しておくべき。
さらにDB処理、ビジネスロジック、プログラム処理と言ったものがあるが、
DB処理はdbhandler専用のモジュールに分けておき、さらにそのモジュールを処理するテーブルごとに分けておいた方が良い(MVCではモデルと言う)
特にビジネスロジックとプログラム処理の区別だが、「商品名にアダルト商品と思わしき文字列があった場合は登録を拒否する」という例外は「ビジネスの例外」であるのに対し、「商品名の文字列がDBで用意されたvarcharの可変文字範囲を超えた」という例外は「技術の例外」であるということを明確に区別するようにコードを書く。