LoginSignup
774
926

More than 3 years have passed since last update.

若手エンジニアを不幸にしないための開発の「べからず」集

Last updated at Posted at 2016-09-10

若手エンジニアを不幸にしないための開発の「べからず」集を書いてみました。

「若手エンジニアを不幸にしないため」とは書いていますが、若手に限った内容ではありません。
いろんな開発の「べからず」のために不幸になるのは、とりわけ若手が多いということを意識したためだと思ったからです。

・若手には、方針の決定権がない。
・若手は、組織の中で道具のように扱われてしまう場合がある。
・(今の)若手は、将来も働き続けるための力を付けるための組織内での教育が、(昔ほど)なされなくなってきている。
・コスト意識が乏しいので必要性が乏しいことについてまで残業前提の仕事のスケジュールを組織がたてることが多い。(その分野の理論を知っていれば自明のことを実験で証明することを要求されるのは苦痛である。)

設計指針の「べからず」

何ができれば十分かを明確にしない

 開発目標は、何ができれば十分なのかを明確にしないまま、追加の項目や、追加の精度向上をだらだらと進めてしまう。
 第1段階は、何ができれば十分。
 第2段階は、何ができれば十分。
 第3段階は、何ができれば十分。
 そういったように、それぞれの段階で目標を明確にもって、それぞれの段階のゴールを達成したというチームでの開発の仕方をしよう。第3段階で実現すべき項目を、第1段階で実現していないからといって責められるのは、責められる側が気の毒だ。
 各段階でゴールを達成したということは、チームメンバーの自信につながっていく。

テストの重要性を考えない設計(あるいは要件定義)をする。

  • そのため、100%の精度を実現できないことが本質的に明らかな項目があるモジュールを使う側の設計がずさんとなる。
  • 上流工程へのテストの追加の流れがあたりまえになってきています。ウォーターフォール型の開発モデルでは、うまくいかないことが多い。そういう流れになってきていることを理解してください。
  • 「問題を必要以上に難しくする項目を受け入れてしまうこと」にもつながる。
  • #ifdefが多数あるプロジェクトをどうテストする?
  • 前工程での不具合に対して、「それはソフトで対応してね」を受け入れる。

現状の設計にある根本的な限界を意識しようとしない。

  • 現状の設計にある根本的な限界がどう将来の拡張に対して影響するのかを考えない。 - > うまくいきそうにない技術
  • 現状の設計を置き換ええる候補を考えてみない 。
  • 技術的な難易度をしかるべき経験のある人物に評価させない。
  • 直感のきく技術者になろう
  • なんでもかんでも、「実験結果がなければ受け入れられない」といいつつ、実験すること自体も拒絶する。
  • 手続き論ばかりになってしまって、その問題に一番詳しい人の意見が多数決で否決される。そして後日、うまくいかなかったときに、「なんでそんな大切なことをもっと誰にでも分かるように言わなかったんだよ。」と言われる。
  • 実際には、「それがどれだけ厄介か」を強く(それなりにわかりやすく言ったつもりでも)言っても、(わかろうとすることを拒絶している人からは)ただの頑固な技術者としてしか見なされない。

  • 問題を扱いやすくするための設計の指針を考えないこと。

    • モジュールの健全性を確かめるためのテスト方法を考えないままモジュールの詳細を作ってしまう。
    • こうあれば便利なのにという設計や実装が可能な案があったとしても、試してみようとしない。
    • 「完成度を高めるとは設計をよりよいものにしていくこと」という意識に乏しく、今までの設計をひたすら守ろうとする。
    • SOLID原則について、聞いたこともなければ、自分の経験から似たような指針を作り出したこともない。

プログラミングでのデータ構造の重要性を意識しない。

  • 標準的なデータ構造を使うことによる利点を意識しない。std::vector<>, std::map<>で十分なことに対して、自前のデータ構造を作ってしまう。

    よいデータ構造でコードを簡潔にする
     独自データ形式の弊害
      標準的なデータ形式を使えば1行で済むことに対して、余計な記述を多数しなければならない。
      その実装上の制約のために、普通ならば簡潔にかけることが、回りくどくなる、ひどい場合には、大多数の普通のプログラムには、そのデータ構造を使うことができなくなることさえある。

  • 項目が追加になったときに対応漏れを生じやすいデータ構造、あるいはそのような関数を利用する。(Open-Closed Principle て何だ)

  • このために項目が追加になったたびに、#defineによるマクロ定数は増え、switch文のcase項目は増え、対応するファイル読み取り、書き出し、デバッグ用の表示ルーチンの対応などが増えてしまい、いつになっても設計が安定しない。

  • テキストデータの読み書きのデータ形式として独自形式を用いて、メンテナンスコストを高めてしまう。項目の記述の順番や記述の有無によって読み取れないなどの問題を生じてしまう。
     それを避けるためには標準的なデータ形式を用いるとよい。テキストデータの形式としては、XMLの他にもYAMLファイルJSONファイルなどのファイル形式があり、使用している言語・ライブラリによっては、これらのファイルを簡単に読み書きできる。保存すべき項目が増えたときの影響を少なく出来る。

  • 言語によっては、データの読み書きに便利な形式があることを活用しない。Pythonの場合のpickleライブラリのように込み入ったデータを一挙に保存する方法があることで、本来の作業に集中できることの利便性に気づかないのはもったいない。

異常系の処理の設計とコーディング、テストの工数が大きいことを理解しない。

 システムを構築する際の難しさは、異常系の処理です。通信プログラムを考えて見ましょう。
通信プログラムの中では、山ほどトラブルを生じる理由があります。そのことにどれだけ対処しているかが
使い物になるライブラリ、ソフトウェアになるかどうかの差を決めていると言っていいと思います。

・指定先の通信先が正しいか?
・通信先が応答を返しているか?
・通信のプロトコールがあっているか?
・通信が途中で途切れたりしていないか?
・通信中にデータが破損していないか?
・通信先が応答を失ったときに、どれくらい復旧を待つべきかどうか?
などなど。

 これらの理由により、何らかのトラブルを生じる側の処理が、生じたトラブルの種類によって多数書かれなければなりません。
 このことが異常系の処理が著しく増える理由です。システムの全体を考えたことのない人が安易に全体の作業量を見積もると、これらの異常系の処理がどれだけ大変であって、どれだけの作業、設計、コーディング、テストが必要になるのかを無視したプランを考えることになります。
 異常系のテストは、発生頻度が少ないものや、その異常を意図的に発生させるのが困難な場合もあります。ですから、正常系の処理に比べて異常系のテストは奥が深いものになります。

 
『幸福な家庭はどれも似たものだが、不幸な家庭はいずれもそれぞれに不幸なものである』という表現があります。

世の中にある既存の技術で利用すべきことの見極めを軽視する。

  • 今までによく利用されて実績のある定式化を無視する。
    • 扱う対象によって、それを得意とする言語・ライブラリを使い分ける価値があることを知るべきこと。
  • 線形代数のライブラリは、実績のある信頼性の高いライブラリを使うべきであって、自分で必要のない自作ライブラリを作ってはならない。
  • 画像処理・画像認識でOpenCVのような定式化されたライブラリを無視して、必然性のない独自実装のデータ形式を使おうとする。結局、画像の縮小のライブラリなどをその独自形式のライブラリも必要になってしまって、自分たちの工数を無駄に使ってしまう。
  • ロボットの分野ではROSが標準のフレームワークになってきている。ROS(Robot Operating System)について調べてみよう
  • 世の中にある既存の技術・知見を利用することで、自分の中の課題を確実に減らすことができるのに、「自分の開発しているのは○○○というまったく新しい技術だから参考になるものは何もないんだ」と考えること。
  • 世の中でソフトウェアの改良が進んでいる枠組みにのっかっておこうとしないこと。のっかていれば、オープンソースのライブラリでの進展をすぐに取り入れやすい状況になるのを無視すること。自力でラベリング関数を書きたくない。
  • 世の中で、ハードウェアの部品技術の進展が進んでいるものを利用しやすい設計にせず、特注の部品を使いたがること。
  • あることを成し遂げるために関連した知識を増やす努力をしないこと。
  • 画像系のソフトウェア技術者の場合、イメージセンサやレンズについてのハードウェアに関する知識を習得しようとしないこと。 - >カメラ関連の自前記事の整理

自作ライブラリは、将来、「技術的な負債」になることがある。

 画像系の技術者の場合、OpenCVを使っていることが多い。
10年以上前に書いたライブラリではOpenCV1.x系統を利用したソースコードになっています。
しかしながら、現在はOpenCV3が最新のライブラリになっています。
OpenCVではIplImage型を使っていたのが、OpenCV3ではcv::Mat型を使う必要があります。
もはや、OpenCV2系統の時代のように、cv::Mat型が推奨であっても、まだIplImageが使えるから
IplImageで通すということはできなくなりました。
 このため、IplImage型で10年以上前に書かれたコードは、「技術的な負債」となりました。
対策1:IplImage型を使ってコンパイル・リンクができる版のOpenCVのソースコードを使って
バージョンを固定させる。
対策2:10年前に書かれたコードを解読して、cv::Mat型に書き直す。
対策3:10年前に書かれたコードを利用することはやめる。

この3つの対応のどれかをすることになります。このため自作ライブラリは
将来「技術的な負債」になることがあります。

物事を成功させるためには、単純化が重要であることを無視する。

  • 複数の厄介さを同時に解決することを目指すこと
  • 動的にカスタマイズできることを重視し、開発の当初に必要なプログラムの簡潔性をそこなうこと。
  • 間違いが入り込む要因をどれだけ減らせるように単純化・小規模化して、早い段階で健全性を確保するかを考えない。
  • 動作するプログラムを作り上げる前に、過度に難易度を上げる制約を加えてしまうこと。
  • KISSの原則
  • 簡単なんじゃない、簡単にするんだ
  • 問題の分析とその中にある簡単化できる要因の切り出しを軽視する。
    • システム全体を理解していないと改良ができないとだけ考える。システム全体を理解していることを個々の技術者に強要する。
      • 実際には、システム全体を理解していないと改良ができないということは、適切な設計になっていないことを意味する。エンジンの仕組みやブレーキなどの仕組みを理解していないと運転できない自動車などいうものがあったら、乗りたいと思いますか。

ものごとを単純化するためには適切な階層化が必要なことを理解しない

ネットワークの階層

ものごとを単純化するには、適切な階層化と抽象化が必要なことを理解していないでソースコードを書くと、メンテナンスが困難な状況に陥る。ものごとを単純化するには適切な階層と抽象化が必要なことは、例えばネットワークを考えればわかるだろう。OSI参照モデルでは、物理層、データリンク層、ネットワーク層、トランスポート層、セッション層、 プレゼンテーション層、アプリケーション層という階層構造を持っている。その階層構造で、各層の役割が明確にされているから、ネットワークというものを単純化することができる。SCPというアプリケーションを動作させるときには、その接続先と、LANケーブルで接続しているのか、無線LANで接続しているのかを意識する必要がない。もし、これが、階層化されていなかったら、SCPのアプリケーションが、有線ケーブル用、無線LAN用と別々のものが必要になるということになってしまったことだろう。
 これと同じようなことがあなたの開発しているアプリケーションで生じていないでしょうか。データベースを必要とするアプリケーションだったら、データベースのライブラリが入れ替わることがあるでしょう。GUIのアプリケーションだったら、GUIのライブラリを時代とともに差し替えなければならなくなることもあるでしょう。そういったときに適切な抽象化がなされていずに、「いわばSCPのアプリケーションが、無線LANの設定を直接いじっている」ような設計になっていることはないでしょうか。

携帯電話開発の事例

もし軍曹が携帯電話をいま開発したらの記事を書いて思い当たるふしがある。山ほどのグローバル変数があるということは、携帯電話の機能を適度に表現したミドルウェアが存在しなかったのではなかろうか。ある程度以上の複雑な処理は適切に階層化された構成で実現されなくてはならないと言っていいだろう。Androidの必然性はそういったところからも来ていたのだろう。
 

十分にできあがっていないときの開発のしやすさを考えない。

  • 機能の組み合わせを「直交性よく」設計しないために、9+9=18ではなく,9*9=81 になるような条件の組み合わせを作ってしまう > 可能性の枝切りができない開発は必ず失敗する。 >開発のしやすさを重視した仕様を作成しよう >開発速度が上がる順序を考慮して実装を行っていこう
  • コードの健全性を確認しきっていない時点で、CPUパワーの制限やIOの制限やディスクスペースの制限など制約の強いターゲットマシンで開発しようとする。
  • うまくいくときに(だけ)うまくいくやり方にこだわり、開発の当初や途中での開発のしやすさを後回しにすること。
  • 「実機がそろわないうちは何もテストできない」はテスト駆動開発では、真ではないことを知らない。
  • ある方式がうまくいかなかったときの逃げを用意しておかない。
  • ハードウェアの選択時に、CPUやメモリなどに十分な余力をもっておかないこと。これらのリソースは選択後に増やすことは難しい。未知の要因があればあるほど余裕をもつことが必要です。
  • stubを使わない。
  • あるものがそろっていないときには、stubというダミーを用意することで、とにかく他の部分での開発をとめないこと。stubには手入力もあるだろうし、テーブルからの参照、事前の固定値、乱数などの中から実現しやすいものを選ぼう。stubを使うことで、他の関数の部分の動作を検証できる。検証できる内容を増やしていくことこそが、開発を前に進めます。
    • 本来的な実装にこだわり、system()関数で他のコマンドを利用することや他言語のライブラリを利用することをしない。まずはアイディアの妥当性を検証することを軽視する。

ウォータフォールモデルで、全てのソフトウェア設計と開発がうまくいくと信じて行動する。

  • 問題の分析が間違ったままだと、害しかなさない要件定義書が実際の開発者を苦しめることになる。
  • ウォータフォールモデルでは、多くの場合なんらかのプロトタイプを作らないために、問題の分析に対して気づかないまま、いたずらに会議だけかさねることになる。
  • 対象とする課題について理解をすることなしに、C++などのコンパイル言語でのウォータフォールモデルの設計をする。
  • どういう状況になると失敗してしまうかを考えずに、うまくいくことだけを前提とした設計をする。
  • 全てがうまくいったときのことだけ考え、ウォータフォール的な手続きにしたがっていることだけを遵守する。
  • そのフェーズでは何を解決するのかを明らかにせず、開発工程のマイルストーンのイベントへの作成をすることだけを、組織運営の目標としてしまう。問題を解決するための試作や実験がなされないまま大規模開発が推進され、使い物にならない納品物に頭を悩ますことになる。

ウォーターフォール開発の課題
プロジェクト・マネジャーが知るべき97のこと 完全な知識という誤った考え

ウォータフォールモデルともアジャイルとも違う流儀の開発があることを知らない。

 ウォータフォールモデルではなかなか動くものが出来上がらない。そのため、ウォータフォールモデルで考えているモデルが、どの程度性能が出るものなのかが分からない。

  •  開発の初期の段階では、実行速度は出なくてもいいけれど、どういう結果がが出るのかを評価できるものがほしい。  
  • 少ない記述量で、目的の計算ができてほしい。
  • 文法が簡単で、動作させやすいものがほしい。
  • 行列演算などの線形代数が、簡潔に書ける。
  • 様々な数値計算の代表的なものが少ない記述で実行できて、しかもそのライブラリの信頼性が高いのがほしい。
  • 入出力を切り替えて、簡単にグラフが書けることがほしい。
  • 制御理論などの枠組みを簡単に利用できること。
  • 計測などの入力を扱いたい。
  • 電気回路のようにブロック図でアルゴリズムを記述できると視覚的に理解しやすい。
  • 必要があれば、簡単にC言語のソースコードを生成できるのがほしい。 - OSはWindowsでもLinuxでも動いてほしい。

 そのような要望を満たす開発手法と、それが出来る言語があります。モデルベースデザインという手法でMATLABという言語です。モデルベースデザインは、車載システムの開発の分野で広く用いられているようです。
 自動車開発で、もはや常識となっているモデルベース開発とは何か?

 このような高信頼性のライブラリを使って少ない記述量でシステムを記述します。そのモデルから、C言語のコードを生成することもできるようになっています。
MathWorks 、MATLAB 言語から直接C コードを自動生成する新製品を発表
MATLAB Coder 2.0

 他にもPythonとNumpyとMatplotlibを使うと、MATLAB類似の開発環境を作ることができます。少ない記述量で、システムを記述できます。今のところ、MATLABがmファイルから組み込み用のC言語のソースコードを自動生成するほどの機能はありませんが、早い段階で動くコードを作って、性能の見通しをつけるという目的には十分に実用的と私は考えています。

分野に応じては、便利な言語が既にある

統計の分野では、S言語、R言語が有用な言語として存在しています。統計の分野での有用な手法は、十分検証されているライブラリを使うことが重要です。教科書に書いてあるアルゴリズムを、C言語、C++言語で自分で実装してみることは、自分の勉強にはなりますが、システム設計の上流での設計の検証を行う目的には推奨しません。S言語、R言語のライブラリは充実しているので、それを使って検証することをお勧めします。

 データベースの分野では、SQLが標準的な枠組みです。
python にはsqlite3というライブラリが標準配布の中に含まれています。既に標準的なものがあって楽に利用できるものは利用しましょう。
11.13. sqlite3 — SQLite データベースに対する DB-API 2.0 インタフェース

コストモデルが設計者の中にない。

どのような設計をして実現させるかには、その対象に対して適切なコストモデルがなくてはなりません。これはハードウェアもソフトウェアも同じことです。その機能の実現にどれくらいコストがかかるのかを知っておく必要があります。ソフトウェアの場合、ハイエンドのCPUの値段だったり、メモリの総量だったり、CPUの処理時間だったり、外部との通信量だったりします。また開発工数のコストだったりもします。どの種類のコストが、今の時点で重要なのかによっては、そのときに採用すべきものが違ってきます。STLがなかった時代だったら、バブルソートは、開発工数が少ないという点でメリットのあるソート手法です。開発の初期段階や、データ数が少ない範囲で使うのに適していました。しかし、大規模データで繰り返し使うライブラリではありません。そのような場合においてもコストモデルがあることが、どの手法を使用して、どの手法は使わないかを考える上で重要なポイントです。

 変数の型がdoubleとfloatで、ターゲットマシンで実行速度がどうであるのかを確かめてみることです。最近のCPUの場合だとdoubleの方がライブラリで高速の場合もあるし、組み込みではfloatの方が速い場合もあります。そのようなことをターゲットマシンで確認して設計のありかたを確認することです。
 pow(x, n) の関数はpow(x,2)などとして使うとx*xよりも極端に遅くなることなど、どのような演算や関数が遅いのか、コストモデルを持つことです。

Intel のCPUの場合には、MicroSoftのコンパイラよりもParallel Studio 2017を使った方が高速に動作することが多いらしいです。

 画像処理や認識の分野でも、どの処理が計算コストが大きいのかをコストモデルを持つことが、アルゴリズムを選択するうえで重要です。そのようなコストについての理解を深めるには、それぞれのライブラリの例題をとにかく実行してみることです。
 むかしだったら、とても遅い処理だったものが、最近のアルゴリズムでは格段に速い処理に置き換わっていることがあって驚くことが多いものです。

原価計算をする際に開発者による開発コストを考えない。

あるモジュールをパーツから自作する場合、それなりの性能をだそうとすると開発要素が増えてしまう。1人の技術者が1年がかりで開発すると、その人の給与・社会保険の負担・人事などの間接部門の負担などの費用がコストとして発生する。だから、部材(あるいは大本のライブラリの部品)だけを集めて、それ以上のモジュールを自作しようとするのはやめたほうがいい。
 そのモジュールを作り上げて、商品としての実用に耐えるものに仕上げるには、あなたが想像する以上にコストがかかる。そしてその分だけ、本来の開発が遅れる原因となる。

余裕のないハードウェアを選択してしまう 

開発内容は、初期の構想段階に比べて膨れていくものです。また実現を約束した作業は、当初の見積もりよりもはるかに手ごわかったりすることは起こりがちです。そういったときに、最初から余裕のないハードウェアを選択してしまうと、開発を格段に難しくしてしまいます。初期の構想段階の見積もりは不正確であることを考慮して、安全係数を考えたハードウェアの性能、処理速度、メモリー量、通信帯域を主張することです。

  • JETSON TX2 モジュール
  • Jetson TK1 TX1について調査中

  • CPUかGPUか専用回路か
    CPUかGPUやFPGAなどの専用回路かと書いていますが、FPGAはよほどのことがないかぎり採用してはいけません。「動くようにする、正しくする、速くする」の速くするなので、一番最後の優先順序に属しているからです。マルチコアのCPU、GPUを使って速くすることをまず考えましょう。OpenCVなどのライブラリでは、マルチコアやGPUでの最適化かされているビルドを使うことです。えてして、画素に対して2重ループでアクセスするコードを書いてしまいがちです。OpenCVの行列演算の関数をよく読んでみましょう。
    画像の処理でなるべく2重ループを書くな

  マルチコアやGPUを使いこなした上でもそれでも速くしたい場合には、アルゴリズムを考えることです。浮動小数点を必要としない固定小数点のアルゴリズムに書き換えることもあります。しかし固定小数点のアルゴリズムに書き換えることは、計算精度の低下をまねきますので、よほどの必要性がないかぎりお勧めしません。

ある方式がうまくいかないときの「逃げとなる方式」を用意しておかない

初期の段階で構想した方式では、実用上不十分なこともある。方式を1つだけしか考えていないと、行き詰まってしまう。そのときになんとか使い物になる「逃げとなる方式」を考えておこう。処理時間は増加するが、確実である方式。扱えない場合もあるが、8割のデータでうまくいく方式。間違えているときに間違えていると分かることで、対策を可能にする方式。うまくと思っていたものでもうまくいかないことはあります。ある方式がうまくいかないときの「逃げとなる方式」を用意しておかないということは、不幸の始まりです。

バージョン管理や差分表示が簡単ではない形式で仕様を記載する。

  • そのため、バージョン管理ツールでドキュメントが更新になったことにも気づかないし、どこが変更になったのかを知る手順がない。
  • メールの添付で届くものは、どれが最新版かわからない。メールでは、新しく加わったメンバーには必要な情報が届かない。
  • バージョン管理せずにサーバーに置かれるものは、いつの間に内容が変わったのかを気づきようもない。
  • Excelで書かれたファイルは、2つのファイルでどこが変更になったかを知る方法がない。
  • ExcelやWord、PowerPointで書かれた仕様書とソースコードのヘッダファイルとに食い違いがあったとき、どちらが最新の有効なものなのかがはっきりしない。
  • ExcelやWordなどはデータ形式の互換性がバージョンアップの際に失われることがある。10年後に読めるファイル形式であるかの確信が持てない。
  •  エクセルやワードの仕様書をマークダウン仕様書に変えよう
  • 公開用のヘッダファイルにDoxygen用のドキュメンテーションコメントを書くことで、仕様書にするというアプローチを私の場合採用している。 ヘッダファイルという名の仕様書

作業の優先順位に対して十分に考えずに結果として間違った指示を出す。

動くようにする、正しくする、速くする。
もぐらたたき開発を卒業しよう

  • その分野の理論を少しでも理解しようとすればやらなくていい作業を、リーダーの開発ポリシーのために実験してみる。
  • 全てのことに対して「実験してみなければわからない」と主張して、その分野に見識を持っている人の行動をことごとく阻害すること。

  • その分野の人間ならば、「実験しなければ分からない」部分を狭めていく必要がある。可能性の組み合わせが枝状に膨れていって収拾がつかなくなるのを防がなくてはならない。

  • 小さな部分の「分からない」を、「まったくわからない」かのように扱うこと。

  • そのことに対して責任を持つ度量がない人が、責任と見識の両方を持っている人の進め方に対して邪魔にしかならない発言をすること。

 すべてのことを論理的に明確に証明しきることができると信じていて、それをチームの進め方に強要する。
 「ある方式が不可能だというなら、不可能だということを証明してくれ」 これは極めて厄介なことです。不可能なことを証明するのは手ごわいことです。「UFOという名の地球外生物の作り出した飛行物体が存在しない」ことを証明するのは、不可能です。証明することができなくても、その技術を開発しているエンジニアの全てが不可能だと言っている場合には、それで不可能ということにしましょう。できるということを示す人がだれもいない状況では、それをものにすることは無理なのですから。言葉遊びをしている暇は、本来の開発目的とスケジュールの中にないのです。

全ての値には誤差がつきものであることを理解しないこと。

  • 誤差が少ないときにうまくいくアルゴリズムが、誤差が大きくなったときに破綻することが多いことを理解しない。
  • 計測・制御・統計・機械学習・画像認識などの分野で仕事をするのならば、誤差についての理解を深めることは必須であることを理解してください。
  • 誤差は、時としてヒントとなる。残差の分析は、対象とする現象とモデルについての理解を進める貴重な情報です。
  • ロバストな手法に興味をもたないこと。>ロバストにいこう

パラメータの違いは、質的な違いを引き起こすことを理解しないこと

  • 定規で測るのと、ノギスで測るのと、マイクロメータで測るのと、寸法であることは同じでも、必要な精度が異なるとまったく別物になる。
  • 10人分の料理を作るのと、100人分の料理を作るのと、1000人分の料理を作るのでは、料理を作るという点で同じであるが、何人分というパラメータの違いが、対処方法をまったく変えさせる。 -化学の分析の実験でも、1gのサンプルの実験と1mgのサンプルの実験と1μgのサンプルの実験とではまったく違ったものになる。
  • 認識率90%のモデルを作ることと、認識率99%のモデルを作るということでは一見同じように見えるかもしれない。しかし、認識率99%のモデルを作る際には、認識率90%のモデルを作る際にはまったく意識していなかった問題点・要因を考える必要に迫られるだろう。
  • プログラムの実行速度を1桁、2桁速くしようとしたら、質的に違ったアプローチが必要になることが多い。

前提条件が変われば、適した手法も変わることを理解しないこと

何か目的に対して適した手法は、前提条件が変われば変わる。前提条件が変わることで、最適な方法は変わる。スズメがえさをつついて食べるときには、えさの位置の奥行きを判断しているとは考えにくい。スズメの目のつき方からすると立体視はほとんどできていないはずである。一方、猛禽類の場合には、左右の目の重なりがあって、立体視ができるようになっている。前提条件が変われば、適した手法が変わっている。あなたが開発しようとしているプログラムについても、前提条件が変われば最適な手法は変わることを理解しましょう。

システムを構築する際には設計の弱点が命取りになる。

 システムを構成する要素が多数にある場合に、システムの命とりになる要素が一つでもあれば、それ以外の部分がどれほど完成度が高くても、ダメである。だから、あるモジュールだけに注意を着目していても、それ以外の部分で問題点をほったらかしにしていると、その部分が命取りになる。
 あるモジュール(ハードウェアであってもソフトウェアであっても)が本来満たすべき機能を満たしていないのを、別のモジュールはかばいようがない。システムを構築するときには、全体の設計がどうあるべきかをきちんと検討して、あるべき設計に直していかなくてはならない。このようなときには、セクショナリズムが組織に蔓延していると、問題点を解決していくことができなくなる。
 

(べからず)実用上十分な水準を目指すのではなく、ベストであることを目指す。

 世の中の大半の人は「ベストであること」をいいことだと思っています。しかし、私は、「ベストであることを目指す」ことは、してはいけないことだと主張します。漠然とした「ベスト」は、開発の目指す目標をあいまいにして、開発チームの時間・体力・予算を奪ってしまう危険な言葉だと思っています。

  • 実用上十分な水準を達成するだけでも大変なのに、(自分は何も作業を分担しないまま)担当の開発者にベストを要求する。
  • あらゆる可能性を考えた上でベストであることを示すというには、極めてやっかいなことです。例:10m 先にある物体の大きさを1cm精度で測定する。これのベストな方法などというのはとてもやっかいなことだと思いませんか。 > 「最適な選択を」求めてはいけない理由の追加

プロトタイピングをしないままC++で実装する

C++でアルゴリズムを実装しようとするときに、いきなりC++で書くことは私はお勧めしない。アルゴリズムが期待どおりの性能がでるのか検証するためには、多くの場合、たくさんのデータについて、アルゴリズムを適用して、結果をグラフにまとめている。Pythonの場合には、matplotlibなどのグラフ作成ライブラリが充実しているので、それらの作業が効率的に実行できる。それらを実行してアルゴリズムの有用性を検証する。つぎにコードをリファクタリングして、正しい設計にしていく。そしてpythonでは楽にプロファイリングができるので、それらの結果をふまえて、スクリプトを作り上げる。その次に、C++でそれを実装する。このようにすると出戻りを生じにくい。検証済みの仕様でC++で実装するので楽になります。

分類中

  • うまく扱いきれていない代物が、改良や改善を拒絶してしまっていることを考えない。
  • 致命的な限界が一つあれば、そのほかの部分の完成度がどれだけ高くても無意味になる。
  • うまく扱いきれていない代物についてのアプローチを先送りし続ける。

問題が明確化できているときは、半分解けている
問題を過度に難しくするな。

定式化の仕方しだいで問題は解きやすくなる
仕様を決定するための実験してエビデンスを残そう。

わかりやすくなければ改善ができない。

  • 現在の設計の中で意味のまとまりがコード上に分散してしまっている。意味のまとまりを見つけ出して、その機能を保守が楽なように作ることで簡潔性が得られることを意識しない。「野放しのグローバル変数」からクラスを見つけ出す
  • 見えるべきデータ構造と隠すべきデータ構造の選択を間違える。

    • 見えるべきデータ構造が隠されてしまったライブラリでは、本来簡単なことが実現不可能になってしまう。
  • 無理なことを「無理」と言わせない状況をつくる。

  • アジャイルな開発が誰にでも常にうまくいくと信じすぎる。(解決しようとする問題に対して理解がなければ、どんなにプログラムのベテランでもどうしようもないよね。「超音波画像でガンかどうかの判定プログラムを作ってください」って言われたって困るよね。)

  • 「他の人が完璧につくってくれれば、僕の部分はちゃんと動くものなるからね。」と言い、(他の人の開発部分に少しでも不具合があったり、開発が完了していないと僕の部分が動かないのは他のメンバーのせいだからね)と主張する開発をしてしまう。

  • 何でもかんでもソフトウェアで実現しようとしてしまう

 ソフトウェアで実現したほうがいいことと、それ以外の方法で実現したほうがいいこととがある。にもかかわらず、何でもかんでもソフトウェアで実現させようとしてしまう。そのことは開発を失敗させるリスクを高めてしまう。

 
- Garbage in, garbage out

あるアルゴリズムがあってデータ解析をする。そのとき、どのようなアルゴリズムであっても、入力データの品質が重要になる。多変量解析でデータを解析するよいライブラリがあったとしよう。その場合でも、入力データの素性が重要だ。多数の写真から、建物の3次元形状を復元する場合でも、写真の画像の品質が重要になる。どのようなアルゴリズムでも、入力データの品質なしに、良好な結果を得ることはできない。Garbage in, garbage out である。

政治的な理由によって、結論が予め決められているのは不幸だ。
 結論のごりおしのために、自由な議論がされません。出来ないことを出来ないと言い切ることができなくなったりします。実験データに対して無謀な解釈をしてしまったりします。開発を担当している人が自由に実験の方向性を模索したり、可能性が高いと信じることをアプローチを推進することもできません。そのような膠着した状況では5年間というまとまった期間が与えられても意味のある結果がだせないことも起こりえます。

すべきこと

  • アルゴリズムの検証には楽な言語を使う。

 ライブラリは移植先の言語と同じライブラリを極力使う。動作時の性能をテストするには作図が便利なライブラリとあわせて使う。動くようにしたあとには、その設計を適切なものにするようにリファクタリングをしていく。classを使った設計に直していって、本番の実装に使える設計のしかたにそろえていく。そのような作業は、そのような目的に便利な言語を使って行う。変数名やメソッド名がそれ自体がドキュメントになるように作っていく。またドキュメンテーションコメントを書いて、そのコードだけで十分に分かるようにしていく。 そうすると、本番の言語が例えばC++であったとしてC++での開発にかかる時間が大幅に短縮できる。
- 例:Python言語、OpenCVの組み合わせはそのような一例です。

  • 組み込みLinuxでの開発の前に,LinuxPCでの開発をしよう。

 LinuxPCで開発すれば、組み込みLinuxと共通のコンパイラや多くの部分で共通のライブラリを使って開発ができます。ソースコードの移植性を高めたまま、高性能のLinuxPCで効果的に開発作業を進めることができます。LinuxPCでの使い勝手を向上させるノウハウは、開発チームで相互に教えあうことで、開発をしやすくしていきましょう。WinMergeに似たツールMeldもあります。

  • バグと手法の限界とは明確に区別しよう    ある雑誌の記事の中に顔画像を入力として血液型を推測する機械学習の例があった。このような場合、コーディング上の問題で結果が間違えているのはバグである。コーディングが適切であるにも関わらず推測結果が性能が出ないのは手法の限界です。それぞれのアルゴリズムには手法上の限界があります。手法の限界とバグとは明確に区別して対策をしなければなりません。手法の限界は、コーディングの不具合ではないので、デバッグ作業で改善することはありません。

 次の事例の場合には、顔つきと体つきに相関があるかもしれないという可能性は無視できないので、
腕に覚えのある人は、データセットやアルゴリズムを変えてみて挑戦するのもありでしょう。とにかく、バグと手法の限界とは別物ですということ。
ディープラーニングで顔写真から巨乳かどうかを判別してみる (うまくいったか微妙)

  • CPUの中で閉じない部分について考えよう    プログラムが実際の社会の中で使われる場合には、その入出力がどのような素性のものかを理解する必要があります。そのことを無視してCPUの中の話をしてもしかたがありません。人は間違った入力をしがちです。センサの場合もケーブルが切れたり、電源が切れてしまって間違った値を返すかもしれません。CPUの中で閉じないことについて、きちんとした対策を考えない限り、実用上十分な装置にはなりません。そういった周辺部分について対策をしなければ「ゴミが入ればばゴミが出る」プログラムしか作りようがありません。

自分の中にコストモデルを作るには

現在使える手法のコストモデルを自分の中に作り上げるお勧めの方法は、オープンソースで提供されているライブラリ・プログラムの例題を実行してみることです。
画像処理や機械学習系のお勧めの1つはscikit-imagescikit-learnです。どちらもPythonのライブラリで、ライブラリの配布元から、それらのライブラリを利用したサンプルスクリプトとサンプルスクリプトを実行するためのデータも含めて配布されています。WindowsでもLinuxでも共通に動作します。

「OpenCV-Python Tutorials」と「実践コンピュータビジョン」とscikit-learn

Qiitaでscikit-learn を検索する

テストの「べからず」

記事が長くなりすぎたので、項目を別記事にしました。

若手エンジニアを不幸にしないための開発の「べからず」集 テスト編

コーディングの「べからず」

記事が長くなりすぎたので、項目を別記事にしました。

若手エンジニアを不幸にしないための開発の「べからず」集 コーディング編

組織運営の「べからず」

記事が長くなりすぎたので、項目を別記事にしました。

若手エンジニアを不幸にしないための開発の「べからず」集 組織運営編


このような状況では、自分の担当外のコードのバグを、いつのまにか結合試験の担当者として本来の自分の担当でない部分のバグ発見(ときとしてバグ修正)するはめになります。
他の人が用意したさまざまな落とし穴にはまり込んでは、一つずつ落とし穴をふさいでは、次の落とし穴にはまりこんでを繰り返してしまうことになります。

「コードを修正するためにはコードの全てを理解しないといけない」という設計は、明らかに間違っています。
そのような設計と開発手法を用いている限り、バグは簡単に埋め込むことができます。
特に、そのコードをプログラム全体から呼び出すことが面倒なコード(つまり単体テストがされにくいコード)であればあるほど、バグが混入しやすくなります。
単体テストを行うことは、理想論ではなくて、できる範囲からはじめていくことです。
自分、そして同僚を不幸にしたくなければ、品質の確保の方法を本気で考えて行動することです。

そのような不幸な開発の状況では、次のようにして問題の切り分けをしていきましょう。以下は私の個人的なアプローチです。

  • 疑わしいインタフェースに対して単体テストを書き始めましょう。
  • 問題を特定できるまで、単体テストを増やしていきましょう。
  • 単体テストができる以前に、実装を変更することは避けましょう。自分が関与する前にあった不具合なのか、自分が埋め込んでしまった不具合なのかが区別つかなくなります。
  • 自分たちの作り上げているモジュールの動作に悪影響を与える要因を減らすことです。えたいのしれないマクロ定数を含んだヘッダファイルをinclude しないことです。

問題が特定できて単体テストが用意できている状況では、次のようにして解決しやすい実装にしていくことができます。

  • Doxygenで該当関数をtopとするcall treeをチェックし、さらに問題の箇所を絞り込むためのテストを追加していきます。
  • 関数の引数にconst属性をつけられるはずと期待できるものには、極力つけていきます。
  • 該当のcall treeの中で単一責務の原則(あるいは単一責任原則)を満たしていないコードがあれば、単一責務の原則にしたがうようにコードを単純にしていきます。
  • その書き換えによって生じたインタフェースに対して単体テストを追加します。そうすることで、どの部分の実装はもっともそうであって、どの実装の部分が疑わしいのかが絞りこまれていきます。単一責務の原則を守っていないコードの場合だと、一所懸命にバグ修正していた部分が、実は呼ばれていない関数だったということになります。
  • 使われていない関数は削除しましょう。そうすることで、不要なコードにまどわされることがなくなります。
  • 公開していないインタフェースについては、ポインタ渡しである必要がない部分は、参照渡しに置き換えます。もちろん、 ここでもconst属性をつけることが可能な引数には、const属性をつけておきます。(公開されているインターフェースで自分の権限のない部分については、そのままにしておくしかありません。)

そのようにすることで、問題の範囲は確実に狭められていくし、作り上げていった単体テストの集まりは、今後、バグが入りこんだときの発見を簡単にしていくはずです。

あなたの結合試験にこのメモが役立つことを期待します。

付記:バグを憎んで人を憎まず。

「べからず」の数がどんどん増えています。しかもそれらの「べからず」が経験に基づいているというのが悲しいところです。

自分の書くコードの品質を高めること、共有するコードの仕様の確認・テストする流儀・修正を反映するやりかたを高めていくこと、これらはソフトウェアエンジニアとして幸せになるための必須の技能です。同僚のよいところを見習って自分の能力を高め、よいやり方を共有することこそが幸せになる方法だと思っています。

・仕様の明確化には単体テストの項目を書くこと有効。

 仕様が言葉だけで十分に明確な表現がされきっていることはまれです。
 
 単体テストを上手に使うと、仕様の明確の明確化ができる。
 言葉による表現では人によって解釈が異なるという問題を回避することができる。

 

自分の近くで生じている修羅場の開発状況をみる状況があったら、自分だったらどうするのかを考えてみましょう。
・どうテストするのか?
・どう厄介な状況を回避するのか?
・どうやってコードの品質を確保するのか?
・開発者が自信に満ち溢れた状況になるのはどういうときか?


[単体テスト軽視のアンチパターン]を見ることは不幸です。「忙しくて単体テストを書いている暇がないから、早く結合試験をしてくれ」とプロジェクトマネジャーが言うことがあるかもしれません。

CppUnitの導入で得られた恩恵

追記 2020年

単体テストの自動化をしないという罪悪が従来型の企業に残っているのはなんとかしたい。
CircleCIなどのツールで単体テストの自動化をしないと、コードを改変するうちにいつの間にか劣化したコードになってしまっているのを気づかない。手動でテストを動作するのを開発者の作業に含めてしまうと、開発時間がそれだけ失われる。次は何をどう改良しよう。次は何を実装しようと思っている内容は、ルーチン作業を繰り返しているうちに、開発者の頭脳の中から揮発してしまう。

774
926
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
774
926