若手エンジニアを不幸にしないための開発の「べからず」集を書いてみました。
「若手エンジニアを不幸にしないため」とは書いていますが、若手に限った内容ではありません。
いろんな開発の「べからず」のために不幸になるのは、とりわけ若手が多いということを意識したためだと思ったからです。
・若手には、方針の決定権がない。
・若手は、組織の中で道具のように扱われてしまう場合がある。
・(今の)若手は、将来も働き続けるための力を付けるための組織内での教育が、(昔ほど)なされなくなってきている。
・コスト意識が乏しいので必要性が乏しいことについてまで残業前提の仕事のスケジュールを組織がたてることが多い。(その分野の理論を知っていれば自明のことを実験で証明することを要求されるのは苦痛である。)
設計指針の「べからず」
テストの重要性を考えない設計(あるいは要件定義)をする。
- そのため、100%の精度を実現できないことが本質的に明らかな項目があるモジュールを使う側の設計がずさんとなる。
- 「問題を必要以上に難しくする項目を受け入れてしまうこと」にもつながる。
- #ifdefが多数あるプロジェクトをどうテストする?
- 前工程での不具合に対して、「それはソフトで対応してね」を受け入れる。
現状の設計にある根本的な限界を意識しようとしない。
- 現状の設計にある根本的な限界がどう将来の拡張に対して影響するのかを考えない。 - > うまくいきそうにない技術
- 現状の設計を置き換ええる候補を考えてみない 。
- 技術的な難易度をしかるべき経験のある人物に評価させない。
- 直感のきく技術者になろう
- なんでもかんでも、「実験結果がなければ受け入れられない」といいつつ、実験すること自体も拒絶する。
- 手続き論ばかりになってしまって、その問題に一番詳しい人の意見が多数決で否決される。そして後日、うまくいかなかったときに、「なんでそんな大切なことをもっと誰にでも分かるように言わなかったんだよ。」と言われる。
実際には、「それがどれだけ厄介か」を強く(それなりにわかりやすく言ったつもりでも)言っても、(わかろうとすることを拒絶している人からは)ただの頑固な技術者としてしか見なされない。
-
問題を扱いやすくするための設計の指針を考えないこと。
- モジュールの健全性を確かめるためのテスト方法を考えないままモジュールの詳細を作ってしまう。
- テスト駆動開発(Test driven development)はテスト手法ではなく、「ユニットテストをうまく利用して、よりよい設計を導くための手法である。」(『テスト駆動開発による組み込みプログラミング C言語とオブジェクト指向で学ぶアジャイルな設計』日本語版まえがき)
- こうあれば便利なのにという設計や実装が可能な案があったとしても、試してみようとしない。
- 「完成度を高めるとは設計をよりよいものにしていくこと」という意識に乏しく、今までの設計をひたすら守ろうとする。
- SOLID原則について、聞いたこともなければ、自分の経験から似たような指針を作り出したこともない。
- モジュールの健全性を確かめるためのテスト方法を考えないままモジュールの詳細を作ってしまう。
プログラミングでのデータ構造の重要性を意識しない。
-
標準的なデータ構造を使うことによる利点を意識しない。std::vector<>, std::map<>で十分なことに対して、自前のデータ構造を作ってしまう。
よいデータ構造でコードを簡潔にする
独自データ形式の弊害
標準的なデータ形式を使えば1行で済むことに対して、余計な記述を多数しなければならない。
その実装上の制約のために、普通ならば簡潔にかけることが、回りくどくなる、ひどい場合には、大多数の普通のプログラムには、そのデータ構造を使うことができなくなることさえある。 項目が追加になったときに対応漏れを生じやすいデータ構造、あるいはそのような関数を利用する。(Open-Closed Principle て何だ)
このために項目が追加になったたびに、#defineによるマクロ定数は増え、switch文のcase項目は増え、対応するファイル読み取り、書き出し、デバッグ用の表示ルーチンの対応などが増えてしまい、いつになっても設計が安定しない。
テキストデータの読み書きのデータ形式として独自形式を用いて、メンテナンスコストを高めてしまう。項目の記述の順番や記述の有無によって読み取れないなどの問題を生じてしまう。
それを避けるためには標準的なデータ形式を用いるとよい。テキストデータの形式としては、XMLの他にもYAMLファイル、JSONファイルなどのファイル形式があり、使用している言語・ライブラリによっては、これらのファイルを簡単に読み書きできる。保存すべき項目が増えたときの影響を少なく出来る。言語によっては、データの読み書きに便利な形式があることを活用しない。Pythonの場合のpickleライブラリのように込み入ったデータを一挙に保存する方法があることで、本来の作業に集中できることの利便性に気づかないのはもったいない。
世の中にある既存の技術で利用すべきことの見極めを軽視する。
- 今までによく利用されて実績のある定式化を無視する。
- 扱う対象によって、それを得意とする言語・ライブラリを使い分ける価値があることを知るべきこと。
- 線形代数のライブラリは、実績のある信頼性の高いライブラリを使うべきであって、自分で必要のない自作ライブラリを作ってはならない。
- 画像処理・画像認識でOpenCVのような定式化されたライブラリを無視して、必然性のない独自実装のデータ形式を使おうとする。結局、画像の縮小のライブラリなどをその独自形式のライブラリも必要になってしまって、自分たちの工数を無駄に使ってしまう。
- ロボットの分野ではROSが標準のフレームワークになってきている。ROS(Robot Operating System)について調べてみよう
- 世の中にある既存の技術・知見を利用することで、自分の中の課題を確実に減らすことができるのに、「自分の開発しているのは○○○というまったく新しい技術だから参考になるものは何もないんだ」と考えること。
- 世の中でソフトウェアの改良が進んでいる枠組みにのっかっておこうとしないこと。のっかていれば、オープンソースのライブラリでの進展をすぐに取り入れやすい状況になるのを無視すること。自力でラベリング関数を書きたくない。
- 世の中で、ハードウェアの部品技術の進展が進んでいるものを利用しやすい設計にせず、特注の部品を使いたがること。
- あることを成し遂げるために関連した知識を増やす努力をしないこと。
- 画像系のソフトウェア技術者の場合、イメージセンサやレンズについてのハードウェアに関する知識を習得しようとしないこと。 - >カメラ関連の自前記事の整理
物事を成功させるためには、単純化が重要であることを無視する。
- 複数の厄介さを同時に解決することを目指すこと
- 動的にカスタマイズできることを重視し、開発の当初に必要なプログラムの簡潔性をそこなうこと。
- 間違いが入り込む要因をどれだけ減らせるように単純化・小規模化して、早い段階で健全性を確保するかを考えない。
- 動作するプログラムを作り上げる前に、過度に難易度を上げる制約を加えてしまうこと。
- KISSの原則
- 簡単なんじゃない、簡単にするんだ
- 問題の分析とその中にある簡単化できる要因の切り出しを軽視する。
- システム全体を理解していないと改良ができないとだけ考える。システム全体を理解していることを個々の技術者に強要する。
- 実際には、システム全体を理解していないと改良ができないということは、適切な設計になっていないことを意味する。エンジンの仕組みやブレーキなどの仕組みを理解していないと運転できない自動車などいうものがあったら、乗りたいと思いますか。
- システム全体を理解していないと改良ができないとだけ考える。システム全体を理解していることを個々の技術者に強要する。
ものごとを単純化するためには適切な階層化が必要なことを理解しない
- ものごとを単純化するには、適切な階層化と抽象化が必要なことを理解していないでソースコードを書くと、メンテナンスが困難な状況に陥る。ものごとを単純化するには適切な階層と抽象化が必要なことは、例えばネットワークを考えればわかるだろう。OSI参照モデルでは、物理層、データリンク層、ネットワーク層、トランスポート層、セッション層、 プレゼンテーション層、アプリケーション層という階層構造を持っている。その階層構造で、各層の役割が明確にされているから、ネットワークというものを単純化することができる。SCPというアプリケーションを動作させるときには、その接続先と、LANケーブルで接続しているのか、無線LANで接続しているのかを意識する必要がない。もし、これが、階層化されていなかったら、SCPのアプリケーションが、有線ケーブル用、無線LAN用と別々のものが必要になるということになってしまったことだろう。 これと同じようなことがあなたの開発しているアプリケーションで生じていないでしょうか。データベースを必要とするアプリケーションだったら、データベースのライブラリが入れ替わることがあるでしょう。GUIのアプリケーションだったら、GUIのライブラリを時代とともに差し替えなければならなくなることもあるでしょう。そういったときに適切な抽象化がなされていずに、「いわばSCPのアプリケーションが、無線LANの設定を直接いじっている」ような設計になっていることはないでしょうか。
十分にできあがっていないときの開発のしやすさを考えない。
- 機能の組み合わせを「直交性よく」設計しないために、9+9=18ではなく,9*9=81 になるような条件の組み合わせを作ってしまう > 可能性の枝切りができない開発は必ず失敗する。 >開発のしやすさを重視した仕様を作成しよう >開発速度が上がる順序を考慮して実装を行っていこう
- コードの健全性を確認しきっていない時点で、CPUパワーの制限やIOの制限やディスクスペースの制限など制約の強いターゲットマシンで開発しようとする。
- うまくいくときに(だけ)うまくいくやり方にこだわり、開発の当初や途中での開発のしやすさを後回しにすること。
- 「実機がそろわないうちは何もテストできない」はテスト駆動開発では、真ではないことを知らない。
- ある方式がうまくいかなかったときの逃げを用意しておかない。
- ハードウェアの選択時に、CPUやメモリなどに十分な余力をもっておかないこと。これらのリソースは選択後に増やすことは難しい。未知の要因があればあるほど余裕をもつことが必要です。
- stubを使わない。
- あるものがそろっていないときには、stubというダミーを用意することで、とにかく他の部分での開発をとめないこと。stubには手入力もあるだろうし、テーブルからの参照、事前の固定値、乱数などの中から実現しやすいものを選ぼう。stubを使うことで、他の関数の部分の動作を検証できる。検証できる内容を増やしていくことこそが、開発を前に進めます。
- 本来的な実装にこだわり、system()関数で他のコマンドを利用することや他言語のライブラリを利用することをしない。まずはアイディアの妥当性を検証することを軽視する。
ウォータフォールモデルで、全てのソフトウェア設計と開発がうまくいくと信じて行動する。
- 問題の分析が間違ったままだと、害しかなさない要件定義書が実際の開発者を苦しめることになる。
- ウォータフォールモデルでは、多くの場合なんらかのプロトタイプを作らないために、問題の分析に対して気づかないまま、いたずらに会議だけかさねることになる。
- 対象とする課題について理解をすることなしに、C++などのコンパイル言語でのウォータフォールモデルの設計をする。
- どういう状況になると失敗してしまうかを考えずに、うまくいくことだけを前提とした設計をする。
- 全てがうまくいったときのことだけ考え、ウォータフォール的な手続きにしたがっていることだけを遵守する。
- そのフェーズでは何を解決するのかを明らかにせず、開発工程のマイルストーンのイベントの作成をすることだけを、組織運営の目標としてしまう。問題を解決するための試作や実験がなされないまま大規模開発が推進され、使え物にならない納品物に頭を悩ますことになる。
コストモデルが設計者の中にない。
どのような設計をして実現させるかには、その対象に対して適切なコストモデルがなくてはなりません。これはハードウェアもソフトウェアも同じことです。その機能の実現にどれくらいコストがかかるのかを知っておく必要があります。ソフトウェアの場合、ハイエンドのCPUの値段だったり、メモリの総量だったり、CPUの処理時間だったり、外部との通信量だったりします。また開発工数のコストだったりもします。どの種類のコストが、今の時点で重要なのかによっては、そのときに採用すべきものが違ってきます。STLがなかった時代だったら、バブルソートは、開発工数が少ないという点でメリットのあるソート手法です。開発の初期段階や、データ数が少ない範囲で使うのに適していました。しかし、大規模データで繰り返し使うライブラリではありません。そのような場合においてもコストモデルがあることが、どの手法を使用して、どの手法は使わないかを考える上で重要なポイントです。
余裕のないハードウェアを選択してしまう
開発内容は、初期の構想段階に比べて膨れていくものです。また実現を約束した作業は、当初の見積もりよりもはるかに手ごわかったりすることは起こりがちです。そういったときに、最初から余裕のないハードウェアを選択してしまうと、開発を格段に難しくしてしまいます。初期の構想段階の見積もりは不正確であることを考慮して、安全係数を考えたハードウェアの性能、処理速度、メモリー量、通信帯域を主張することです。
ある方式がうまくいかないときの「逃げとなる方式」を用意しておかない
初期の段階で構想した方式では、実用上不十分なこともある。方式を1つだけしか考えていないと、行き詰まってしまう。そのときになんとか使い物になる「逃げとなる方式」を考えておこう。処理時間は増加するが、確実である方式。扱えない場合もあるが、8割のデータでうまくいく方式。間違えているときに間違えていると分かることで、対策を可能にする方式。うまくと思っていたものでもうまくいかないことはあります。ある方式がうまくいかないときの「逃げとなる方式」を用意しておかないということは、不幸の始まりです。
バージョン管理や差分表示が簡単ではない形式で仕様を記載する。
- そのため、バージョン管理ツールでドキュメントが更新になったことにも気づかないし、どこが変更になったのかを知る手順がない。
- メールの添付で届くものは、どれが最新版かわからない。メールでは、新しく加わったメンバーには必要な情報が届かない。
- バージョン管理せずにサーバーに置かれるものは、いつの間に内容が変わったのかを気づきようもない。
- Excelで書かれたファイルは、2つのファイルでどこが変更になったかを知る方法がない。
- ExcelやWord、PowerPointで書かれた仕様書とソースコードのヘッダファイルとに食い違いがあったとき、どちらが最新の有効なものなのかがはっきりしない。
- ExcelやWordなどはデータ形式の互換性がバージョンアップの際に失われることがある。10年後に読めるファイル形式であるかの確信が持てない。
- 公開用のヘッダファイルにDoxygen用のドキュメンテーションコメントを書くことで、仕様書にするというアプローチを私の場合採用している。 ヘッダファイルという名の仕様書
作業の優先順位に対して十分に考えずに結果として間違った指示を出す。
動くようにする、正しくする、速くする。
もぐらたたき開発を卒業しよう
-その分野の理論を少しでも理解しようとすればやらなくていい作業を、リーダーの開発ポリシーのために実験してみる。
- 全てのことに対して「実験してみなければわからない」と主張して、その分野に見識を持っている人の行動をことごとく阻害すること。
- その分野の人間ならば、「実験しなければ分からない」部分を狭めていく必要がある。可能性の組み合わせが枝状に膨れていって収拾がつかなくなるのを防がなくてはならない。
- 小さな部分の「分からない」を、「まったくわからない」かのように扱うこと。
- そのことに対して責任を持つ度量がない人が、責任と見識の両方を持っている人の進め方に対して邪魔にしかならない発言をすること。
全ての値には誤差がつきものであることを理解しないこと。
- 誤差が少ないときにうまくいくアルゴリズムが、誤差が大きくなったときに破綻することが多いことを理解しない。
- 計測・制御・統計・機械学習・画像認識などの分野で仕事をするのならば、誤差についての理解を深めることは必須であることを理解してください。
- 誤差は、時としてヒントとなる。残差の分析は、対象とする現象とモデルについての理解を進める貴重な情報です。
- ロバストな手法に興味をもたないこと。>ロバストにいこう
パラメータの違いは、質的な違いを引き起こすことを理解しないこと
- 定規で測るのと、ノギスで測るのと、マイクロメータで測るのと、寸法であることは同じでも、必要な精度が異なるとまったく別物になる。
- 10人分の料理を作るのと、100人分の料理を作るのと、1000人分の料理を作るのでは、料理を作るという点で同じであるが、何人分というパラメータの違いが、対処方法をまったく変えさせる。 -化学の分析の実験でも、1gのサンプルの実験と1mgのサンプルの実験と1μgのサンプルの実験とではまったく違ったものになる。
- 認識率90%のモデルを作ることと、認識率99%のモデルを作るということでは一見同じように見えるかもしれない。しかし、認識率99%のモデルを作る際には、認識率90%のモデルを作る際にはまったく意識していなかった問題点・要因を考える必要に迫られるだろう。
- プログラムの実行速度を1桁、2桁速くしようとしたら、質的に違ったアプローチが必要になることが多い。
(べからず)実用上十分な水準を目指すのではなく、ベストであることを目指す。
世の中の大半の人は「ベストであること」をいいことだと思っています。しかし、私は、「ベストであることを目指す」ことは、してはいけないことだと主張します。漠然とした「ベスト」は、開発の目指す目標をあいまいにして、開発チームの時間・体力・予算を奪ってしまう危険な言葉だと思っています。
- 実用上十分な水準を達成するだけでも大変なのに、(自分は何も作業を分担しないまま)担当の開発者にベストを要求する。
- あらゆる可能性を考えた上でベストであることを示すというには、極めてやっかいなことです。例:10m 先にある物体の大きさを1cm精度で測定する。これのベストな方法などというのはとてもやっかいなことだと思いませんか。 > 「最適な選択を」求めてはいけない理由の追加
分類中
- うまく扱いきれていない代物が、改良や改善を拒絶してしまっていることを考えない。
- 致命的な限界が一つあれば、そのほかの部分の完成度がどれだけ高くても無意味になる。
- うまく扱いきれていない代物についてのアプローチを先送りし続ける。
問題が明確化できているときは、半分解けている
問題を過度に難しくするな。
定式化の仕方しだいで問題は解きやすくなる
仕様を決定するための実験してエビデンスを残そう。
- 現在の設計の中で意味のまとまりがコード上に分散してしまっている。意味のまとまりを見つけ出して、その機能を保守が楽なように作ることで簡潔性が得られることを意識しない。「野放しのグローバル変数」からクラスを見つけ出す
- 見えるべきデータ構造と隠すべきデータ構造の選択を間違える。
- 見えるべきデータ構造が隠されてしまったライブラリでは、本来簡単なことが実現不可能になってしまう。
- 無理なことを「無理」と言わせない状況をつくる。
アジャイルな開発が誰にでも常にうまくいくと信じすぎる。(解決しようとする問題に対して理解がなければ、どんなにプログラムのベテランでもどうしようもないよね。「超音波画像でガンかどうかの判定プログラムを作ってください」って言われたって困るよね。)
「他の人が完璧につくってくれれば、僕の部分はちゃんと動くものなるからね。」と言い、(他の人の開発部分に少しでも不具合があったり、開発が完了していないと僕の部分が動かないのは他のメンバーのせいだからね)と主張する開発をしてしまう。
何でもかんでもソフトウェアで実現しようとしてしまう
ソフトウェアで実現したほうがいいことと、それ以外の方法で実現したほうがいいこととがある。にもかかわらず、何でもかんでもソフトウェアで実現させようとしてしまう。そのことは開発を失敗させるリスクを高めてしまう。
バグと手法の限界とは明確に区別しよう
ある雑誌の記事の中に顔画像を入力として血液型を推測する機械学習の例があった。このような場合、コーディング上の問題で結果が間違えているのはバグである。コーディングが適切であるにも関わらず推測結果が性能が出ないのは手法の限界です。それぞれのアルゴリズムには手法上の限界があります。手法の限界とバグとは明確に区別して対策をしなければなりません。手法の限界は、コーディングの不具合ではないので、デバッグ作業で改善することはありません。CPUの中で閉じない部分について考えよう
プログラムが実際の社会の中で使われる場合には、その入出力がどのような素性のものかを理解する必要があります。そのことを無視してCPUの中の話をしてもしかたがありません。人は間違った入力をしがちです。センサの場合もケーブルが切れたり、電源が切れてしまって間違った値を返すかもしれません。CPUの中で閉じないことについて、きちんとした対策を考えない限り、実用上十分な装置にはなりません。そういった周辺部分について対策をしなければ「ゴミが入ればばゴミが出る」プログラムしか作りようがありません。
テストの「べからず」
記事が長くなりすぎたので、項目を別記事にしました。
若手エンジニアを不幸にしないための開発の「べからず」集 テスト編
コーディングの「べからず」
- 何を実装すべきなのかについての詳細を確認しない。
- 実装のコーディングの前に、個別の関数への仕様が十分に明らかになっているかを確認しない。
- ヘッダファイルに記述されたドキュメンテーションコメントをも誰でもが曖昧さなしに解釈できる内容になっているかをチェックしない。
- 実装すべき内容は、どのようにテストしたらよいのかをコーディング前に考えない。
- アイディア検証を軽視する。
- アイディア検証には、信頼性の高い別ライブラリ・別言語を使って楽をするなどという可能性を考えない。
- アイディア検証の時点では、グラフ作成や高次の機能でのassertが楽にできる言語・ライブラリを使うと格段に楽になります。制限の強いライブラリで見ていると、問題に気づきにくくなります。
- 実装したい機能を実現する方法を1つ考えたら、それ以上の可能性を考えようともしない。
- アイディア検証の時点で、テストをしておくことでバグの可能性を減らせることを考えない。
- アイディア検証には、信頼性の高い別ライブラリ・別言語を使って楽をするなどという可能性を考えない。
- ビルドに潜む厄介さに対して対策を打たない
- if(ブール定数){}で十分なことを#if #endifで条件付きコンパイルしてしまう。
- #if #endifで条件つきコンパイルを増やしてしまい、いつの間にかビルドさえできないソースコードを作り出してしまう。
- 条件付きコンパイルのための指定をソースコード中に#defineで記述してしまうので、コードの利用者が条件付きコンパイルのための指定がばらついているときに、予期しない#defineの組み合わせでコードをビルドしてしまう。
- const で十分なところを#defineを用いてしまう。
- CMakeを使わない。
- コミットするのは、ビルドができてからというルールを徹底させない。
- 隠蔽をしすぎる。
- プログラムで生じる例外を区別せずに、全部まとめて例外を捕捉する。
- バグに気づきうるかもしれない項目も含めて、全てを隠蔽してしまう。
- 単体テストをするならば必要になるインタフェースを関数の抽出をしていない。
- 単体テストを軽視する
- 変換と逆変換が存在するのに、その動作の単体テストを実施しない。
- 再現性のある単体テストが不可能なcamera captureのインタフェースでしか用意しない。
- 単一責務の原理を無視しまくっているので、テスト用のコードと本来の実装のコードがいつのまにか乖離しまくっているので、テスト用のコードが意味をなさなくなっている。単一責務の原理(Single Responsibility Principle)を守るとバグに苦しむことが減る
- こうすればきれいな実装になるはずと、単体テストを用意せずに設計変更して泥沼に導いてしまうこと。
- デバッグ目的の機能が、本来の実装の重荷になるような設計をしてしまう。
- for文とif文の組み合わせで条件にヒットしなかったときの可能性を考えていない。
-
コーディングをやっかいにしないための方策を取り入れようとしない。
- 意味を誤解するような関数名・変数名を使い続けること。
- 1割増しの困難をもたらすコード上のやっかいさを何十個と含めてしまう。
- イテレータを使うことで簡潔に書ける部分でイテレータを使わない。
- プログラムの動作状況や原因追跡のログを書くコードの中でfflush()を実施しない。
- 型の指定を間違えてときにトラブルを生じやすいprintf(), scanf()を使うこと。
- 副作用のある引数を多用してしまう。関数名や引数名からするとconstであることが期待されるのに副作用を持っていると、利用者は値が書き換えられていることに気づかず、原因不明のバグに苦しむ。
- std::max(), std::min()を使わず、if文や3項演算子を使う。
- std::cout を使わず、printf() を使う。得てして、printfの型指定を間違えてしまう。
- __FUNCTION__ マクロ定数を使わずに、表示の文字列の中でべた書きする。
-
古すぎるライブラリ
- 他に山ほど処理時間のかかる部分があるのに、std::stringはchar*型よりオーバーヘッドが大きいと気にすること。
- Boostなどの有用なライブラリをもちいれば、OS依存性の問題を避けられるのに、windows.hなどに含まれている機能を使うこと。
- UnixのC言語のライブラリは、ネットワークや文字コードが進展する前の大昔のコードを多く含んでいるのに、それが有名なテキストに含まれている関数だからといって使い続ける設計を行う。
- 利用できるライブラリに対して無関心でありつづけること。
- 対象のコンパイラではサポートしている機能があるのに、無意味に古いコンパイラで動作させようとすることにこだわる。
- 微々たる「高速化」のために、enum型やdefineによるマクロ定数を乱発して、エラーメッセージがわかりにくくなる設計をする。 > マクロ定数が多すぎる。 > マクロ定数が多すぎるへの対策
-
最終ターゲットと開発環境
- 最終ターゲットと違いすぎる環境で長期間開発しすぎること。
- とりわけソフトウェア環境として違いすぎること。OSやC++などの利用するコンパイラや利用するライブラリが違いすぎることが開発の手間を増やしてしまう。コンパイラの種類によらず適切なC++のコードを書くのは理想かもしれないが、何もGCCとVisual StudioのC++の最新規格への対応状況を自分の仕事のプロジェクトの中に持ち込みたくはない。GCCで十分であるのならば、GCCだけを使って、PC-LinuxとARMの組み込みLinuxに対応できる限り対応するほうがいい。
- 最終ターゲットが貧弱な環境であるのにもかかわらず、最初からその貧弱な実環境での開発にこだわりすぎること。
- アイディアの検証をする必要がある時点で、最初から過度に最終ターゲットの実環境と限定されたライブラリだけを使ってアイディアの検証をしようとする。
- 最終ターゲットと違いすぎる環境で長期間開発しすぎること。
-
コーディング時点でのテストの軽視
- コンピュータ以外の部分含まれている開発で、周辺機器のIOを含む部分での単体テストのコードを書かない。
- 繰り返し試験を行わなければならないライブラリのテストを十分な自動化をしないために不十分なテストになってしまうままのコーディング・テストマシンの運用状況を作ってしまう。
- テストマシンとしては、RaspberryPiなどのマシンが安価に調達しやすいということに気づかない。
- RaspberryPI の産業用途
- RaspberryPi でPythonでモータ制御するための情報
- CNN(Convolutional Neural Network) on Raspberry pi
- RaspberryPi でPythonでモータ制御するための情報
既存のコードをリファクタリングしないままに、機能を追加する。
その結果、1つのクラスもしくはモジュールの役割が不明確になりがちです。大概の場合は、追加した機能によって、単一責務の原理から外れてしまいがちです。いつのまにか、コードの明快さがなくなってしまいがちです。そうなることを防ぐために、まずリファクタリングすることです。そして、その後で機能を追加します。追加する機能が、既存のクラスもしくはモジュールに、ちょっと異質の要素がある場合には、別のクラスもしくは別のモジュールにして、その差分が明確になるようにして実装したほうがいいと感じています。既存のコードをリファクタリングしないままに機能を追加すると、コードを書いている本人にとっても理解不能なコードになっていく危険なアプローチです。
組織運営の「べからず」
記事が長くなりすぎたので、項目を別記事にしました。
若手エンジニアを不幸にしないための開発の「べからず」集 組織運営編
このような状況では、自分の担当外のコードのバグを、いつのまにか結合試験の担当者として本来の自分の担当でない部分のバグ発見(ときとしてバグ修正)するはめになります。
他の人が用意したさまざまな落とし穴にはまり込んでは、一つずつ落とし穴をふさいでは、次の落とし穴にはまりこんでを繰り返してしまうことになります。
「コードを修正するためにはコードの全てを理解しないといけない」という設計は、明らかに間違っています。
そのような設計と開発手法を用いている限り、バグは簡単に埋め込むことができます。
特に、そのコードをプログラム全体から呼び出すことが面倒なコード(つまり単体テストがされにくいコード)であればあるほど、バグが混入しやすくなります。
単体テストを行うことは、理想論ではなくて、できる範囲からはじめていくことです。
自分、そして同僚を不幸にしたくなければ、品質の確保の方法を本気で考えて行動することです。
そのような不幸な開発の状況では、次のようにして問題の切り分けをしていきましょう。以下は私の個人的なアプローチです。
- 疑わしいインタフェースに対して単体テストを書き始めましょう。
- 問題を特定できるまで、単体テストを増やしていきましょう。
- 単体テストができる以前に、実装を変更することは避けましょう。自分が関与する前にあった不具合なのか、自分が埋め込んでしまった不具合なのかが区別つかなくなります。
- 自分たちの作り上げているモジュールの動作に悪影響を与える要因を減らすことです。えたいのしれないマクロ定数を含んだヘッダファイルをinclude しないことです。
問題が特定できて単体テストが用意できている状況では、次のようにして解決しやすい実装にしていくことができます。
- Doxygenで該当関数をtopとするcall treeをチェックし、さらに問題の箇所を絞り込むためのテストを追加していきます。
- 関数の引数にconst属性をつけられるはずと期待できるものには、極力つけていきます。
- 該当のcall treeの中で単一責務の原則(あるいは単一責任原則)を満たしていないコードがあれば、単一責務の原則にしたがうようにコードを単純にしていきます。
- その書き換えによって生じたインタフェースに対して単体テストを追加します。そうすることで、どの部分の実装はもっともそうであって、どの実装の部分が疑わしいのかが絞りこまれていきます。単一責務の原則を守っていないコードの場合だと、一所懸命にバグ修正していた部分が、実は呼ばれていない関数だったということになります。
- 使われていない関数は削除しましょう。そうすることで、不要なコードにまどわされることがなくなります。
- 公開していないインタフェースについては、ポインタ渡しである必要がない部分は、参照渡しに置き換えます。もちろん、 ここでもconst属性をつけることが可能な引数には、const属性をつけておきます。(公開されているインターフェースで自分の権限のない部分については、そのままにしておくしかありません。)
そのようにすることで、問題の範囲は確実に狭められていくし、作り上げていった単体テストの集まりは、今後、バグが入りこんだときの発見を簡単にしていくはずです。
あなたの結合試験にこのメモが役立つことを期待します。
付記:バグを憎んで人を憎まず。
「べからず」の数がどんどん増えています。しかもそれらの「べからず」が経験に基づいているというのが悲しいところです。
自分の書くコードの品質を高めること、共有するコードの仕様の確認・テストする流儀・修正を反映するやりかたを高めていくこと、これらはソフトウェアエンジニアとして幸せになるための必須の技能です。同僚のよいところを見習って自分の能力を高め、よいやり方を共有することこそが幸せになる方法だと思っています。
自分の近くで生じている修羅場の開発状況をみる状況があったら、自分だったらどうするのかを考えてみましょう。
・どうテストするのか?
・どう厄介な状況を回避するのか?
・どうやってコードの品質を確保するのか?
・開発者が自信に満ち溢れた状況になるのはどういうときか?
[単体テスト軽視のアンチパターン]を見ることは不幸です。「忙しくて単体テストを書いている暇がないから、早く結合試験をしてくれ」とプロジェクトマネジャーが言うことがあるかもしれません。