oshiro の日記

I love good unittest, low level programing and nice team

やはり目標設定とは難しく、そして重大な問題でもある

先日下のような記事を読んで、これは組織の目標設定の持つ構造的な欠陥によって本来の目的と真逆の行動を行ってしまうこともあり得るのかと思い、私が毎回目標設定シーズンに頭を抱える理由と重なるところもあったので一度なぜ目標設定が難しいのか整理して考えてみる。

間違えやすい標識で交通違反に…「警察の“間違えるのを待って”取り締まる姿勢に納得できない」――大反響トップ3 | 日刊SPA!

記事の内容について簡単にまとめてみると、誰もが間違うような標識や複雑な交通ルールが適用される場所で警察が取り締まりを行っているが本来警察とは交通人が間違う前に停めたり誘導したりすべきだという内容である。

組織としての警察

まずもって重要なのは警察とは何をすべき組織であるか、一般の民営企業(以降企業とする)でいうところのミッションやバリューである。 こちらについては警察法第一条及び第二条に下のように定められている。

(この法律の目的) 第一条 この法律は、個人の権利と自由を保護し、公共の安全と秩序を維持するため、民主的理念を基調とする警察の管理と運営を保障し、且つ、能率的にその任務を遂行するに足る警察の組織を定めることを目的とする。 (警察の責務) 第二条 警察は、個人の生命、身体及び財産の保護に任じ、犯罪の予防、鎮圧及び捜査、被疑者の逮捕、交通の取締その他公共の安全と秩序の維持に当ることをもつてその責務とする。 2 警察の活動は、厳格に前項の責務の範囲に限られるべきものであつて、その責務の遂行に当つては、不偏不党且つ公平中正を旨とし、いやしくも日本国憲法の保障する個人の権利及び自由の干渉にわたる等その権限を濫用することがあつてはならない。

筆者は法律の専門家ではないのでこれがどう解釈されているかについて深く理解しているところではないが、警察組織とは公共の安全と秩序の維持が目的の組織であり、その手段及び責務として交通取締が含まれているものと読めそうだ(繰り返すが筆者は法学徒ではないのでこの解釈に責任は持てないので悪しからず)し、広く一般的な警察という認識と相違ないと考えてよさそうである。

そして人事評価についてであるが、これは都道府県警ごとに詳細は異なりそうだが、評価指標としては大きく異ならないだろうという認識で確認する。 ある程度人事評価関連資料へのアクセスが容易であった愛知県警を例とする。
そもそも警察での昇進は昇進試験に基づいて実施されるため、人事評価として昇給を検討し、その対象としては最も現場で良く遭遇し、ある程度経験のある警察官として巡査部長の職位を対象にする。

愛知県警では人事評価について「愛知県警察職員人事評価実施要綱の制定」により下記定められている。

第2 人事評価の意義 この要綱において、次に掲げる用語の意義は、それぞれ次に定めるところによる。

(1) 人事評価 任用(採用、昇任、降任及び転任をいう。以下同じ。)、給与、分限その他の人事管理の基礎とするために、職員がその職務を遂行するに当たり発揮した能力及び挙げた業績を把握した上で行われる勤務成績の評価をいう。

(2) 能力評価 職員の標準職務遂行能力(愛知県警察職員の標準的な職及び標準職務遂行能力に関する規程(平成28年愛知県警察本部訓令第16号)第3条に規定する標準職務遂行能力をいう。)に対する評価をいう。

(3) 業績評価 職員がその職務を遂行するに当たり挙げた業績に対する評価をいう

そしてこの「標準職務遂行能力」についても下記のように定められている。

倫理: 全体の奉仕者として、責任を持って業務に取り組むとともに、服務規律を遵守し、中立公正に職務を遂行することができる。

事案対応: 担当業務に必要な知識・技術を習得し、事案に適切に対応することができる。

協調性、報告・連絡: 上司・部下等と協力的な関係を構築し、適切な状況報告、連絡等を行うことができる。

業務遂行: 計画的に業務を進め、確実に業務を遂行することができる。

組織としての職位に期待される行動や能力がかなり詳細化・具体化されており、かなり公正に近い評価指標を持つ組織と言えそうである。

組織の目的と個人の目標の乖離

ではなぜこれだけ詳細な評価指標を持つ警察組織であっても、誰も幸せにならない本来発生せずに済んだ取り締まりが発生してしまうのだろうか。
これは警察組織に限った話ではなく、目標設定の持つ構造的な欠陥に由来するものであると考えている。

本来警察組織の目的は「公共の秩序と安全の維持」であり、状態を指しているわけである。
世界から犯罪が消失し、交通事故もすべて無くなった状態が成立すれば警察の目的は達せられたといえるであろう。
しかし、犯罪や交通事故というものは決して無くならず、外部要因として潜在的に存在し続けるものである。
そしてこれらは時間や場所という要素にも大きく左右されるものであり、結果、つまり対象となる外部状態で評価する(完全な結果目標)と警察官の能力や努力に依存しない部分で差が出てしまう。

例えば莫大な予算をかけて作った精鋭部隊を歌舞伎町に配属した場合と、著しく能力の低い人材を元々治安のよい地域に配属した場合に、担当地域の犯罪発生件数や死傷者数では恐らく前者の方が圧倒的に高い数値となることは想像に難くない。
もちろん結果を残せていないため昇給に値しないという評価を下すこともできよう。
しかし、個人にできることは限度があり、個人の職務領域外での要因により左右された結果を比較することは非科学的な考えである。

つまり優秀な人材であっても結果としては評価されないという事象が発生してしまい、公正さに著しい疑義が起こる。
これはなぜかというと、 一律でない外部要因が大きく影響する結果に対して結果目標を評価してしまった ためである。 組織の目標を個人の目標へ落とし込んだにもかかわらずである。

個人を公正に評価することと、組織の評価とは明確に一致しないことが分かる。

そして多くの組織ではこのような評価基準は採用されていないだろう。

個人の評価では結果以外に何を評価すればよいのか

では個人をどう評価すべきなのか、何を以てして評価すればよいのだろうか。

筆者は個人の目標は行動目標であるべきだと考える。 つまり何をしたかということを評価指標とすべきである。

行動目標とは個人が何をするかという基準のため、外部要因をある程度排除した評価結果を返すことが可能である。
現場の警察官であれば、一日X回パトロールするとかY時間トレーニングするなどであろうか。

しかしこれは個人差があり過ぎて公平性を保つことは難しく、特に強く結果を求められる組織や下部組織に所属している場合は適さない場合も多く、コントロールが難しい。

そのために重要な存在となるのが管理職、マネージャーなどのいわゆる上司である。
マネージャーの職務としてはプレーヤー個人の行動目標が組織もしくは下部組織の結果目標につながるようにコントロールすることである。

仮に企業の営業部を例にとるとすれば、営業部の目標が売り上げ 10,000,000 円とした場合に、営業部員それぞれに 1,000,000 円の結果目標をかけるのではなく、それぞれが 1,000,000 円を売り上げるために必要なアクションを洗い出して、それを実際に行動に移せることを目標とすべきである。
純化すると、ある営業部員が10回に1回 100,000 円の商談を成立させる程度の成績だった場合、10,000,000 円の売り上げには単価を変えずに 100 回アポイントメントが必要であり、それが行動目標となるわけである。
つまり、行動目標はすべての行動目標を達成すれば結果目標を達成できるように行動目標を設定されるべきである。

このことからマネージャーは外部要因を予測して結果と行動を結びつけることが必要な資質であると言える。

一方で組織特性によっては完全に外部要因を排した個人目標では成り立たないことも当然存在する。
先に述べたように組織に求められる結果が外部要因によって大きく影響されるべきでない組織がそうであり、警察やインフラ企業などはこれに該当するだろう。
今年は物価が上がったためそれを外部要因として犯罪率が上がってしまった、原料高騰のため僻地への燃料・電気の供給を停止するというのは、なかなか許容し難いところもあるだろう。
彼らは安定して結果を出すことを責務として求められているのである。
そのために個人にも一定程度結果を評価指標に取り込むということはあり得る話であり、そのために巡査部長の評価指標として検挙率/数を使用するとなったとしても大きな疑問はない。

では、評価指標として予防措置を取り入れればよく、こちらも定量化でき十分に公平であるということもできる。
しかし、予防措置というものを評価することはかなり難しく、コストもかかるという欠点がある。
声かけによってどの程度犯罪を予防できたか、起こるはずの犯罪が起こらなかったかということを統計的に有意に評価するためには声かけの状況なども詳しく報告し、解析する必要がある。

恐らくそのための仕組みを取り入れるための時間と費用で一律昇給させた方が良い結果が得られる程度にはコストに見合わない可能性が高い。

やはり検挙数などは実務のフローを考えても評価指標として非常に使い勝手の良い指標であることが分かる。

言い換えると組織とは結果を評価されるべき存在であり、個人ではプロセスを評価すべきなのである。
そのためにマネージャーは被評価者の特性と組織の目標をよく理解し、個人のプロセスが組織の目標達成に寄与しうるかどうかを判断する責任があると言える。
その上で警察組織とは非常にシビアに結果を求められ、かつ公正でオープンな評価がなされなければならない組織であるという特性上、検挙・取締結果が個人の評価につながってしまうということはある程度構造上必然的であるとも考えられる。 そのため、事前の予防措置という不透明な結果よりも検挙数という、公平性のある数字を目標としており結果として前書きの記事のような結果が生じてしまったのではないだろうか。

個人の目標設定には妥当性・コスト・公平性という重要な要素があり、これらをバランスよく考えて組織に最も合致した評価方法を考えなければならない。

公正で公平な個人の評価について検挙・取締数以上にコストがかからず、公平な評価指標が構築されない以上、これからも記事のようなことは発生し続けるだろう。

組織の健全性について考えてみた話

特にこれと言ってモチベートされたわけではないのですが, 職務経歴書とか転職活動の準備とかをぼんやりと考えていた際に徒然に浮かんでは消えた話などをまとめてみようと思いました.
学術的な正確さとかを追及しているわけではないので, 誤った事実等があればコメントお願いします.

健全な組織とは

組織の健全性を構成する要素として以下のように考える.

  • 自己組織化されていること
  • 意思決定過程が明確化されていること
  • 多様性が保障されていること

組織の自己組織化

自己組織化の典型とは生物であり, 本論では組織をその類似性より生物的なモデルから検討する。 まずはこの「自己」とは何であるかを定めなければならないが, この定義はその活動に必要な機能を内部に保有する系とする.
つまり単なる集合を自己とするのではなく, ある目的に対する機能単位について自己ということができる.

例を挙げて考えてみる.
顧客Aに提供するシステムの開発を例にとると, 開発1課と営業1課が顧客Aの担当であるとすれば, このシステム開発チームの指す自己とは「開発1課の PM と開発者」ではなく, それに加えて顧客とのチャネルを持つ営業担当者までを含めての範囲となる.
これは企業の評価軸や所属で観測すると「開発1課(という自己組織) + 担当営業」という考えになるかもしれないが, 本論ではそのような集合については「組織」の対象としない.

では自己組織化した組織とはどのような組織であるのか.
自己組織化の概要を以下に示す.

  • 外部に対し開放系であること

    環境に対して開かれていること.
    どのような組織も自組織外である他社や他組織, ユーザーとの関わりの中において存続している. 外部とのコミュニケーションに閉鎖的であり独自の進化を遂げることは組織の衰退となる.

  • 組織が非平衡状態にあること

    平衡状態は自然的な状態であり安定しているが, 故に組織の平衡状態とは生命にとって死を意味する.
    平衡状態にある組織は行動を起こすことができず, 強力な外部からのエネルギーによってのみ活動可能な状態に復帰できる.

  • 自己が再帰的な定義であること

    組織の構成要素もまた組織である.
    しかし, 最終的な構成要素となる各メンバーについてかくあるべきと求めるわけではない.

ではなぜこのような自己組織化が健全な組織の条件となるのか. それは上記のような自己組織化した組織は自らの力によって成長し, 活動し続けることが可能だからである.

外部に対し閉鎖的であれば新たな情報やエネルギーを取り入れることができず, 再帰的に自己が定義されていなければ自己増殖(組織のスケールアウトやスケールアップ)時に同一性を保つことができず, 一様に目的を果たすことができなくなる.
平衡状態化した組織においては新規に発生する問題に対応することができず, 変化し続ける組織を取り巻く環境に適応できなくなる. 故に組織が自己組織化していることは組織健全において必要な要因であるといえる.

一方でこの自己組織化は組織目的達成のための観測可能な状態に過ぎず, 企業経営的な観点から定量指標として採用することは難しいともいえる.

意思決定における明確さ

組織運用とはあらゆる意思決定の実行である. そして明確さとは主に決定基準の明確性とそのプロセスの透明性であると考える. 計画・設計・実行・評価などの組織の行為は多数の可能性の中から一つ, もしくは複数を選択する行為である.

あらゆる意思決定はその決定プロセスのみで存在するのではなく, その決定の評価や振り返りなども含めて存在する. 故にこの意思決定における透明性とは以下の2つの観点より重要性が説明できる.

  • 決定者の判断基準として
  • 決定における被影響者の納得感

以上2点について検討する.

判断基準としての明確さ

これは自身が意思決定者たる場合にあてはまる. 例えば QCD(Quolity, Cost and Delivery) というメトリクスを採用して物事を検討しているとする. 多くの場合はこれらの3要素は互いに成立せず, いわゆるトレードオフの関係になる.
そしてこれらの決定が概ね等価であれば意思決定者としては判断するのは非常に難しい. なぜならどの選択をしても得るものと捨てるものがそれほど変わらないからである.
残念なことに, えてしてこれらの選択肢の検討で時間を消費することは資源の浪費である.

このような場合には明確な判断基準が必要となる. このケースであれば, 高単価であるが高品質なサービスという判断軸があれば, 迷うことなく選択できる. これは単に簡単に素早く意思決定できるというだけではなく, 意思決定後の評価についても大きく影響する.

特に失敗した場合に, 意思決定そのものについての責任の所在が明らかになる. 品質を取りコストを捨てた結果失敗したのであれば, それは判断による失敗ではなくターゲッティングのミスであると判断できる. なぜならば組織の判断基準に従って判断したのであれば, 誰が判断しても品質を取る結果になったからである.

もちろんこれは QCD を分析指標として使用したこと, そしてその分析結果が正しいものであるということは前提であり, 様々な文脈を考慮しなければならないことはいうまでもない.

被影響者の納得感

意思決定は社内コンペや人事評価, 提案の競合など組織内部に向けて行われることも珍しくない. そして多くはその決定によって誰かが不利益を被ることになる. 評価や選考がブラックボックスに行われていれば, 不利益となった側にとっては相当なストレスと不信感となる.
また, 基準が明確でなければどのように準備をしそれをプレゼンすれば良いかも不明瞭である.
不明瞭な目的に立ち向かうということは, 風車に突撃するドン・キホーテと相違ない.

このような状況では被評価者の費やした準備が徒労に終わるだけでなく後々まで遺恨を残すことになりかねない. 特に日本人は「自分の内的状態が他者に実際以上に明らかになっていると過大評価する(鎌田)」傾向にあり, 対人相互作用として実際以上の影響を与えることになる.
つまり本人は「これだけ準備を念入りにして臨んだのに, よくわからない理由でリジェクトされて自分のものよりも劣る提案が採用された」という遺恨だけが残りかねないわけである.
評価者と被評価者がお互いに「そうなのだろう, お前の中ではな」と考えあうという最悪の状況を引き起こす.

そのためにも判断のプロセスとその基準については可能な限り明確になっていることが望ましい.

多様性の保障

最後に多様性の保障がなされていることである. 日本では多様性, つまり divercity について以下のように理解されている

「ダイバシティとは『従業員の多様性』のこと. 性別, 人種, 国籍, 宗教など, 異なる背景や価値 観を持つ人々がともに働くことで生産性を向上し, 創造性を高めていこうという考え方」 (労基旬報 平成 15 年 10 月 15 日号)

筆者は多様性の研究者ではないし活動家でもなく, 多様性そのもに関する話を展開することはしない.

健全な組織であるためには, そのメンバーを社会的背景(性別や国籍, 出身地等)や必要以上の個人的な事情によって制限することがあってはならない.
マイノリティを理解仕様とする姿勢や優遇制度などの必要は無いと考えるが, 育児や進学, 介護などといった個人的な事情への配慮やそのような扱いへの不平不満が出ないような環境を作り, 組織のパフォーマンスの最大化を考えられる文化的土壌が望ましい.

参考文献

海老沢栄一『企業評価の新指標 -組織健全性概念を中心として-』日本経営診断学会年報(1999) Vol.31 鎌田晶子『透明性の錯覚: 日本人における錯覚の生起と係留の効果』The Japanese Journal of Experimental Social Psychology(2007) Vol.46 山口智彦 『さまざまな自己組織化とその工学的応用』, 表面技術(2011)

正論は殴るべき武器ではなく、進むべき道を照らす灯台である

正論は殴るべき武器ではなく、進むべき道を照らす灯台である

※ポエムです

筆者の主張

前提の確認

 まずこの手の話題に触れる前に前提を固めておきます。 この記事では「正論」とは とある議論の場において問題の達成もしくは問題解決に対して到達点のみに焦点化した手段の主張。またはあるべき状態の主張 とします。つまり「Aという問題に対してはBすべき」のような論を指します。 また、議論中における正論について論じますが議論参加者は当該の立場において、必要なリテラシーを備えている善意の人物であることを想定しています。

主張

 筆者の主張は表題通りですが「正論なき議論には意味はあるが正義はなく、新たな問題を生む」というものです。正直に言って極論ですね。別にここまでの過激派ではありませんが、立場を明らかにするためにいったん言い切ります。

 これはどういうことかと言うと、とある目的が存在しておりその何かしらの目的を達成するために議論をするのであり、そこにはすべての悪を排した正論が必要ということです。 悪というのは予算であったり納期であったりという、一般的な制約や条件を指しています。議論をするためには誰もが「そうあったらいいのにな、けど現実はそうじゃないよね」という理想を用意することが重要です。

 一般的な議論やミーティングなどでは議題が存在します。議題は「予算の割り振り」や「システムアーキテクチャの構成」であったり、概ね解決すべき問題や何かを決定することでしょう。おそらく多くの場合には発起人が作成した草案が存在していて、それを基にしてこの数値は高すぎるとかこの構成箇所は欠陥があるとかといってそれを全員でより良いものに変更していくという方法で進めていくものです。
 学生であれば教官と論文の研究手法や比較対象を設定するために似たようなプロセスを踏むことでしょう。

 もちろんこのプロセスは否定すべきものではなく、十分効率的で意味のある建設的議論です。しかしこの方法はえてして近視眼的な議論となり、直近の問題を解決はできるものの中長期的にはその解決策に起因する別の問題を引き起こします。
 極端な例ですが、世界平和と武器根絶を謳う集団が当面の運転資産の確保のために武器売買や闇取引で資金を獲得してもそれは長期的にはマイナスの効果しか生み出しません。世界平和実現のための手法で資金を獲得できる手法を考えるべきであるというのは正論ですが、これが考慮されていればもっと別の手段を考えることができたかもしれません。システム開発であれば問題解決のために別のツールを導入することで、結果としてより複雑な問題を引き起こすかもしれません。
 また、研究活動であれば本来社会的に有意義な研究活動をしたいと思っていてそのために新たな化学反応式を発見するという目的があったとしても、学位論文の締め切りや競争的資金の獲得のために論文を書くということそれ自体が目的となり従来の反応式に適当なパラメータを変更しただけの式に関する論文として完成させてしまうこともあるでしょう。

 これをモデル化して考えると、議題とはつまり問題でありそのための手段を議論していると言えます。ここには主要な構成要素である目的が欠損しています。つまり全ての問題にはその問題の解決によってどうなりたいのかという目的が存在します。
 すべての問題解決は問題を解決するために存在するのではなく、目的を達成するために存在するというより上位の目的が見失われています。そして多くの場合では問題解決によって達成したい目的というのはより上位の目的に結びついているものであり、問題を解決する手段というのは上位目的に反するものであってはいけません。企業であれば全ての活動は経営理念の達成のための活動であり、決して利益獲得が目的ではないはずですね。
 利益や経済的成長は経営理念達成活動のための資金を確保する手段であり、それ自体が達成すべき目的ではないはずです。しかし、上位目的がとある意思決定の場において失われれば、より下位の資金獲得という目的が最重要な目的となってしまいます。そうなると長時間労働やグレーゾーンの侵犯、不正行為などが行われてしまいます。

 以上より正論とは自身含め集団をあるべき姿として保ち、その振る舞いを規定するものであると言えます。このような意味で正論とは「より正しい方向へ議論、ひいては自分たちを導くための灯台」となるべきものであるという主張でした。

なぜ正論は嫌われるのか?

 とはいえ現実を忘れて概念と化した正論が宗教戦争や戦争の正当化、大量殺人を行ってきたように、現実を見据えない正論も同様に人を傷つけます。正論は「最も有効な手段、もしくは状態」であるが故に、それを達成・実行することは常に困難です。上記の主張を見た方の中には制約を取り払って考えてもそれには意味が無いと思う方がいても不思議ではありません。
 我々はイデアに存在するのではなく、現実の存在であるため常に干渉しあって存在しています。一方だけが存在を主張することはできません。貴重な時間と頭脳という資源を消費して生産した結論が机上の空論では意味がありませんので、現実解を出さなくてはいけません。他者や制約を無視して自分だけに都合の良い、あたかもそうあるべきであるかのような主張を行うことはしばしば正論と呼ばれ、忌避されるべき言論となります。
 これらの言は誤りかそうでないかというと誤りではありません。しかし、その言葉には目的達成のため意味が含まれているかというとそうではありません。
 近年目にする機会の増えてきた学校の一見無意味な校則などはこれに当てはまるものだと思っています。化粧や派手な服装などは近年規制が緩くなってきていますが、まだ女子生徒の下着は白のみなどの校則があるという話も耳にします。これらを『校則は守るべきだから』と言ってしまうのは一見正しそうです。しかし、これはどこに目的があるのでしょうか。この校則を守った結果どうなりたいのかはまるでわかりません。
 服装の自由を許す程度で校内の風紀や規律が乱れるというのは学校経営力の無さの話ではないでしょうか(学校関係者各位には申し訳ありませんが、学校単位の話ではなく学校制度の話として捉えていただけると幸いです)。これは本来学校の風紀や規律を正す役割を持つ者が自らの怠惰を正当化するためにそれらしく仕立て上げた「目的のない正論」であるといえます。

正論は主張するものではなく比較である

 では議論において正論をどう扱えばよいのでしょうか。筆者は正論とは基準であり、比較対象であると考えます。
 我々はどうあるべきか、どうふるまうべきかという指標こそが正論であり、現状とあるべき姿の乖離を浮かび上がらせることができます。実務においては問題こそが問題となりがちです。しかしその問題というのは上位目的からしてみれば分岐点に過ぎません。複数ある分岐の中からどの道を進むのかという決定においては、様々な要因が関わってきます。予算や納期、充てられる人材や設備などの制約の中で最適解を出すことを求められます。ここで決定を下すということは非常に難しいことです。
 多くの場合はある程度絞り込まれたどの分岐に進んでも結果は一つしかなく、誤りであることを立証できないという点が意思決定の難しさです。この意思決定というプロセスにおいて重要になる基準が正論です。

 先の平和団体の例で考えてみます。目下の資金として1,000万円必要であったとします。そのための方法として以下の3つが考案されました。

  1. 募金活動を拡大する
  2. 高単価で需要のある商品の売買をする
  3. 保有資産を運用する

 これらはどの方法でも1,000万円を用意できるとします。資金獲得という目的を達成できるという点ではどれも正解でしょう。しかし、最も確実に早く実現できるからといって 2 を選択するのはどうでしょう。最速で資金調達ができたとしても組織体が崩れることで運営危機を招きかねないので選択肢から外れます。では 1 と 3 ではどうでしょうか。平和の理念を掲げて募金活動をすることは賛同者の獲得や広報にもなり、自分たちの価値を最大限発揮しながら資金を獲得できます。
 しかしこの選択肢には 1 点問題があります。現実的でないということです。募金や寄付だけで目標額を達成するにはかなりの人員と年月がかかります。ようやく資金が集まったころには本来やりたかった活動ができなくなっているかもしれません。

 では 3 の選択肢はどうでしょう。一見すると自分たちの理念には整合しませんが、かといって矛盾しているわけでもありません。資産運用はあくまでも理念達成活動を行うための手段であり、自分たちの持つ資源や時間という制約を考慮すると最も早く資金を獲得して自分たちが本来持つ目的を実現する機会を得ることができます。

 正論という灯台の明かりは方向を示してくれますが、その道中を保障するものではありません。光の指すままに進み続けると強大な障壁に直面する可能性もあります。

正論で殴ってくる相手に立ち向かう

 前節で触れたように正論は向かうべき方向を示してくれるがその道中を保障しません。現実を無視し正論を振りかざすという行為は道を尋ねられた際に「右です」と答えるようなものであり、慈善団体は募金と寄付活動以外で収入を得てはいけないというようなものです。
 しかし、実際には無意識のうちに正論を振りかざす人に出会うことは少なくありません。ミーティングで進捗の良くない人に対して仕事ぶりがよくないからもっと効率的に働かないといけないという指摘をしたり、研究報告に対して研究は新規性があるべきだがこの研究には新規性がないから無意味だと言い放ったり、これらは指摘内容が正しく重要な意味を持っている正論です。しかし、東京に行くには東に行かないといけないのにあなたは北に向かっていますよと指摘されたところで、おそらく本人もそんなことは知っています。しかしなんらかの理由で東に向かえないので北に向かってしまっていたのでしょう。新幹線代を稼ぐためにバイトを探していたのかもしれませんし、そもそもコンパスがなく東がどちらか分からないのかもしれません。

 正論に正論で立ち向かうのは愚策です。相手の目的地が正しいことを認め、一緒にどのような道程で進めばよいかを考えましょう。そして正論を振りかざすだけで道程を考えない人は相手を変えたいという意思はなく、自分は間違っていないという確認をしたいだけです。

さいごに

 正論とは灯台の灯であり、羅針盤です。当たり前に存在し、進むべき道を知っているひとには必要性について意識することもないものでしょう。しかし、道を新たに切り拓く開拓者や標を失った迷い人には進む先を教えてくれる重要な機構です。みんなで幸せになるために道を間違えずに進むものであり、誰かを否定するためにふるう人が 1 人でも減ってくれることを切に願います。

stylish な dockerfile にしよう

この記事は Docker Advent Calendar 20日目の記事になります.
Advent Calendar もいよいよ終盤ですが, 皆様の docker life の一助になれば幸いです.

dockerfile が stylish でなければいけないわけ

まず docker image を何故使用するのかという話なんですが, 基本的にはイメージを使ってビルドや CI を回したいからですよね.
すると docker pull に時間かけるのって凄いもったいないんです.
アプリで何とかしてテストケース絞ったり並列化したりして実行時間を減らそうとしてるのに, コンテナビルドでその時間を食い潰したくないわけですね.

じゃあどうやってビルドを高速化するかということなんですが, もちろん方法はいろいろあります.
今回は dockerfile をダイエットさせて高速化を図ろうという企画です.

予め期待感を明らかにしておくと, 超高速化したり10GB のイメージを100MBにするような銀の弾丸を紹介することはできません.
しかし, 1 コンテナのビルドを 30 秒高速化したとして, 10 コンテナあればそれは5分の改善になります.
小さな改善をたくさん行うことが重要だという思想で本記事を執筆しています.

let's breaking 2 !!

docker コンテナが構築される仕組み(essential ver)

まずは簡単に docker image の仕組みを説明します.
結論を言うと, docker image は複数のレイヤー(イメージ)が重なって構成されています. docker pull をすると

$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
7ddbc47eeb70: Already exists
c1bbdc448b72: Already exists
8c3b70e39044: Already exists
45d437916d57: Already exists
Digest: sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

ubuntu のイメージを pull すると, この時4つのイメージ(7ddbc47eeb70 c1bbdc448b72 8c3b70e39044 45d437916d57)がpull されていることが分かります. これを docker history で見てみると下記のように各コマンドを実行した形跡(レイヤー)が残っています.

$ docker history ubuntu
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
775349758637        6 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           6 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           6 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           6 weeks ago         /bin/sh -c [ -z "$(apt-get indextargets)" ]     987kB
<missing>           6 weeks ago         /bin/sh -c #(nop) ADD file:a48a5dc1b9dbfc632…   63.2MB

中間イメージはタグが切られているので, missing になってしまっていますが, ubuntu の公式イメージが上記で pull してきたであろう 4 イメージと CMD による 実行レイヤーによって構成されているであろうことがみてとれます.
dockerfile でいうと dockefile 内に書かれた命令一つ一つが新しいイメージのレイヤーを生成することになります.
新しいレイヤーはコンテナを起動する際にも作成されます. この新しいレイヤーはそれまでのレイヤーのイメージを使い Dockerfile の命令を実行して新しいイメージでコンテナを起動する際ことで生成されます.
そして基本的には中間コンテナを破棄して最終的な静的コンテナを生成します. つまり我々が docker pull なり build なりをして得ているものはこの最終生成されたイメージに過ぎないが, その過程ではレイヤー数分の中間コンテナやイメージを作成したりプルしたりしているということです.

軽量な dockerfile はきれいとは限らない

装飾を捨てる

そして, docker に過剰なパワーは必要ありません.
Base イメージを見直して見ましょう.
結論を先に述べると, alpine linux を使うと幸せになれる確率は高いです. 以下に主な Base イメージのサイズを示します.

image time size
ubuntu 7.5s 64.2MB
debian 10s 114MB
fedora 12s 194MB
alpine 4.8s 5.55MB

やはり alpine が劇的に高速 & 軽量ですね. alpine は非常に軽量ではありますが, apk というパッケージマネージャーを持っているので, apt ほどパワフルではないですがパッケージを使用することも可能です. 詳細を知りたい方はぜひ調べてみてください.

これは公式イメージにも当てはまります. 例えば ruby では 無印の rubydebian ベースで作成されていますが, ruby:alpine を使用したものも配布されています.

image time size
ruby:2.6 39s 840MB
ruby:2.6-alpine 7.8s 50.9MB
python:3.7 40s 917MB
python:3.7-alpine 8.1s 98.4MB

これを全て実行するとわかるんですが, pythonruby の公式イメージの最初の 5 レイヤーは同じレイヤーを使用しており, ここが 30s 以上使用しています.

これはあくまで運用のコンテキストでのお話です.
開発環境を docker で提供している場合には, alpine ではパワー不足に陥る可能性もあります. ちょっとリッチなデバッグツールや測定ツールを使いたいとなった際に apk で提供されていなかったり, 最新バージョンの配布がされておらず, ソースからコンパイルしてインストールする必要性に迫られる可能性は結構あります.

筆者は開発環境ごと docker で提供していて ubuntu ベースのコンテナもあります.
筆者の入社前から稼働しているコンテナなどは ubuntu ベース ですが, 特に alpine に置き換えて困ることもなさそうです.
ある程度成熟してるコンテナだとリプレースコストに成果が見合わないかも知れません.

レイヤを結合する

上で説明したように, docker イメージはレイヤー構造をもっています.
そしてレイヤー自体もイメージなので, レイヤーを減らすとサイズを小さくすることができます.
例えば RUN を一つにまとめることができます.

RUN apt update \
  && apt install -y \
    git \
    curl \
    gcc

これだと5つのコマンドを実行していますが, 生成されるレイヤーは一つだけになります.

そして, curl や git などはパッケージやプロジェクトのソースを取得するのには必要だが, その後の運用には必要ないので削除したい.
しかし, 普通に削除するだけでは軽量化にはならないことに気を付けなければいけません.
このデモを書くために ubuntu でビルドしてるんですけど, 2回くらい apt update してもう辟易しています.
キャッシュを使わずに使い捨てる環境であれば, 重いイメージ使うのはやめましょう、、、

例えば下記の dockerfile を比べてみます.
10MB のファイルを削除した分軽量化できていそうです.

# version1
FROM ubuntu:latest

RUN dd if=/dev/zero of=/10megafile bs=1M count=10 && rm /10megafile

# version2
FROM ubuntu:latest

RUN dd if=/dev/zero of=/10megafile bs=1M count=10
RUN rm /10megafile
$ docker images
REPOSITORY     SIZE
ver1              64 MB
ver2              74 MB

どっちもファイルを削除していますが, ver2 の方がちょうど10MB大きくなっています.
更にレイヤーを調べてみます.

$ docker history ver1
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
5ff21765e225        2 minutes ago       /bin/sh -c dd if=/dev/zero of=/10megafile bs…   0B
775349758637        6 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           6 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           6 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           6 weeks ago         /bin/sh -c [ -z "$(apt-get indextargets)" ]     987kB
<missing>           6 weeks ago         /bin/sh -c #(nop) ADD file:a48a5dc1b9dbfc632…   63.2MB

$ docker history ver2
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
7dca59e53772        3 minutes ago       /bin/sh -c rm /10megafile                       0B
58645d76c7a7        3 minutes ago       /bin/sh -c dd if=/dev/zero of=/10megafile bs…   10.5MB
775349758637        6 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           6 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           6 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           6 weeks ago         /bin/sh -c [ -z "$(apt-get indextargets)" ]     987kB
<missing>           6 weeks ago         /bin/sh -c #(nop) ADD file:a48a5dc1b9dbfc632…   63.2MB

ver2 ではファイルを作成するレイヤーと削除するレイヤーがそれぞれ作成されており, 10.5MB のレイヤー作成レイヤーの上に0MBの削除レイヤーが乗っているだけで軽量化されていないことがわかります. つまり clone が終わった後の git や fetch した後の curl なんかも同じ RUN コマンドの中で削除してやるとイメージサイズを増やさずに使うことができます.

運用ルートを理解する

docker -v HOST_DIR:DOCKER_DIR で docker のマウントポイントを明示的にホストのディレクトリにすることで, Dockerfile の仕事をかなり減らすことができる可能性もあります.
ポータビリティは下がってしまいますが, CI のように想定したパスに存在していることが保障されているのであればポータビリティの低下はさほど問題にはなりません.

その代わりに git の install や git clone を実施する手間を削減できる方がおいしい場合が多いです.

dockerfile のデバッグ

以上が dockerfile スタイリッシュ計画になります.
ただ, dockerfile を作るのに何度もビルドを繰り返しながら進めていくのは大変骨が折れる上にキャッシュとの折り合いがつかなくて泥仕合を繰り広げかねないので, そこだけ最後に追記して本記事の結びといたします.

dockerfile のデバッグは雑に言うと失敗の直前のレイヤを実行すればいいよというただそれだけの話です.

FROM busybox:latest

RUN echo "Hello world!"
RUN /bin/bash -c echo "goodbye world"

上記 dockerfile を実行すると, error で止まります.

$ docker build . -t test
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM busybox:latest
latest: Pulling from library/busybox
322973677ef5: Pull complete
Digest: sha256:1828edd60c5efd34b2bf5dd3282ec0cc04d47b2ff9caa0b6d4f07a21d1c08084
Status: Downloaded newer image for busybox:latest
 ---> b534869c81f0
Step 2/3 : RUN echo "Hello world!"
 ---> Running in df9276262164    <--- docker が作った一時コンテナ
Hello world!
Removing intermediate container df9276262164    <--- 一時コンテナを削除
 ---> 19a3306dc73f     <--- コンテナから作成されたイメージID
Step 3/3 : RUN /bin/bash -c echo "goodbye world"
 ---> Running in e9cb02ef7052
/bin/sh: /bin/bash: not found
The command '/bin/sh -c /bin/bash -c echo "goodbye world"' returned a non-zero code: 127

なのでこの場合は最後に作成されたコンテナを実行してみます.

$ docker run -it 19a3306dc73f
/ # /bin/bash -c echo "goodbye world"
sh: /bin/bash: not found

同じログが出ている例で申し訳ありませんでしたが, これで sh が bash を呼び出せずに build が失敗していたことがわかります. これで毎回 docker build を実行して try & error で泥臭く進める必要がなくなりました!

是非皆さんも快適な docker life をお過ごしください!

CI/CD ってよく聞くけど...?

自動化好きなひとや QA をされているひとはもちろん知っているかと思いますが, 自動テスト文化の無い開発エンジニアのひとや社内 SE をされているひとの中には「聞いたことはあるけど, よくわかんないや。。。」なんて声もあるかも知れません.
今回は CI を中心に記事を書きます.

ターゲットは自動テストの導入を検討している方や既にパイプラインはあるけど, 担当したことが無くてよくわからないという方ですね.
ゴールは CI/CD をキメると幸せになれるメカニズムを理解してもらうことです.


CI/CD ってそもそも何?

CI/CD は Continuous Integration/Continuous Delivery の略です.
これは概念を指す言葉であって, 特定のツールや操作をさすものではありません.

日本語でいうと継続的インテグレーション / 継続的デリバリー です. カタカナにしただけじゃないかというご指摘はもっともなのですが, 如何せんこの訳が定着してしまっている上に抽象的に的確な表現の言葉が見つからないので, すいません.

CI/CD って何をしてくれるの?

それでは CI/CD て結局なんなのかということなのですが, 要はテスト何回もしてたくさんデプロイして本番稼働してるサービスからフィードバック得てより良いサービスにしていこうということです.

CI は継続的インテグレーションだと書きましたが, これは

開発メンバーが作った機能はすぐに開発コードにマージして他の機能に影響を与えていないか(デグレを起こしていないか)をテストを回すことですぐに検知しよう

という考え方です.

  • 継続的 -> 何度も
  • インテグレーション -> 統合する/くっつける

つまり何度も 開発 -> マージ -> テスト -> 開発 -> マージ -> .... のようにこのサイクルを回すことで早期にバグを発見して取り除いていくことで高速開発を目指します.

開発 -> 開発 -> 開発 -> ..... -> 統合 -> テスト のような開発だと統合してテストを行って不具合が発見されるても. どこが原因かを探ることが非常に困難になってしまいます.

継続的にインテグレーションされていれば, 自分の開発した機能をマージして不具合がでたら自分のコミットを見直すことでその不具合を発見することができますよね.

そして CD ですが, これは基本的に CI が機能し始めてからのステップの話で, セットで出てくるけれど概念としては別のものくらいの認識で問題ないでしょう.

CD は継続的デリバリーだと書きましたが, 多分多くの方は「要するになんなの?」と思われるでしょうね.
これも読んでそのままで, 開発環境で動いているコードを違う環境に持っていくこと( Delivery )することです.

CI によって我々が必死に書いたコードは(ある程度)問題なく開発環境で動いていることが保証された状態です.
なので検証環境で動かして開発チーム以外の人にも見てもらって要件を満たしているか見てもらう必要があります(もちろんこの限りではないです).

近いうちに自動テストについて詳細に書きたいと思っていますが, 開発者の動かす自動テストは万能ではないので, QA さんや運用の人に見てもらったりしてより多くの気づきをもらうことが重要です.

主にステージング環境ではシナリオテストを実施したり探索的テストを行ったりすると思いますが, ここは企業やチームによって大きく異なるでしょう.

決して「機械が実施したテストなんて信用できない!人間がすべて目で見て確かめてエクセルにエビデンス(スクショ)を張れ!」っていう偉い人を納得させるためにCDを行うわけではないですよ.

CI/CD が達成されると何が嬉しいのか?

CI/CD が自動化されて運用されるようになると, 高速でプロダクトライフサイクルを回転させることができます. アジャイル開発にとっては何度も小さくリリースしてフィードバックを得るかが成功のカギになります.
もちろん機械が自動でできることは基本的に人間の手でも可能です. ですが毎回人間の手でそれを実現することははっきりいって無駄です. ぼやっといっても無駄です.

要するに何回も繰り返し実施しなければいけないことで, しかも些細なミスも許されないようなオペレーション(テストやリリースでミスオペは大惨事につながります)から解放されることが最大のメリットです.

  • 反復と精緻が要求される作業を機械化することで再現性が担保される
  • 実行者に関わらず誰が実施しても常に同じ結果が得られる

この2点が CI/CD のメリットと言えるでしょう.

本音を言えば面倒な作業(人力のリグレッションテストやリリース作業)から逃れられるのが最強の理由ですが, それではえらい人を説得するのは難しいので上の理由をもっともらしく説明するのがいいかも知れません.

CI って具体的に何をすればよいの?

テストを書きましょう.

自動化の仕組みとかそういうのは置いておいていいので, まずはテストコードを書いてください.

自動テストをする

今回は自動テストの中でもキホンのキであるユニットテストの導入だけ解説します.
ユニットテスト単体テストとも呼ばれていて, 関数が期待通りに動作することを保証します.

ざっくりこんな感じです.

# src/calc.py

class Calc: 

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def add(self):
        return self.a + self.b
# test/test_calc.py

from src.calc import Calc

class TestCalc:

    def test_add_01():
        assert Calc(9, 2).add() == 11

    def test_add_02():
        assert Calc(-9, 2).add() == -7

これで pytest とルートディレクトリで打つだけでテストが実行されます.
誰かが気づかずに Calc クラスを壊してしまってもテストが失敗するのでデグレをマージせずにすみます.

class Calc:

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def add(self):
+        return self.a - self.b
-        return self.a + self.b

それ, pytest 実行だ!くらえ!

$ pytest
======================================== test session starts ========================================
platform win32 -- Python 3.7.4, pytest-5.3.0, py-1.8.0, pluggy-0.13.1
rootdir: C:\WORKSPACE\test
collected 2 items                                                                                    

test_calc.py FF                                                                                [100%]

============================================= FAILURES ==============================================
_______________________________________ TestGroup.test_add_01 _______________________________________

self = <test_calc.TestGroup object at 0x000001F1E40B3E88>

    def test_add_01(self):
>       assert Calc(9, 2).add() == 11
E       assert 7 == 11
E        +  where 7 = <bound method Calc.add of <test_calc.Calc object at 0x000001F1E40B3AC8>>()
E        +    where <bound method Calc.add of <test_calc.Calc object at 0x000001F1E40B3AC8>> = <test_c
alc.Calc object at 0x000001F1E40B3AC8>.add
E        +      where <test_calc.Calc object at 0x000001F1E40B3AC8> = Calc(9, 2)

test_calc.py:3: AssertionError
_______________________________________ TestGroup.test_add_02 _______________________________________

self = <test_calc.TestGroup object at 0x000001F1E40A7AC8>

    def test_add_02(self):
>       assert Calc(-9, 2).add() == -7
E       assert -11 == -7
E        +  where -11 = <bound method Calc.add of <test_calc.Calc object at 0x000001F1E40A7F88>>()
E        +    where <bound method Calc.add of <test_calc.Calc object at 0x000001F1E40A7F88>> = <test_c
alc.Calc object at 0x000001F1E40A7F88>.add
E        +      where <test_calc.Calc object at 0x000001F1E40A7F88> = Calc(-9, 2)

test_calc.py:6: AssertionError
========================================= 2 failed in 0.06s =========================================

こんな感じでテストが失敗していて, どういった失敗だったのかということを教えてくれます.
とっても簡単です.
リファクタリングし放題です.

良いテストを書くよりもまずはテストが動いていることが大切なので, 細かいことを考えずにテストを書きましょう.

どうやって実現するの?

ある程度テストが増えてきたら, 定期的に実行したくなると思います. なっているはずです.

ここで初めて Jenkins だったり CircleCI だったり GithubAction だったりというツールの出番になります.

このようなツールの詳細は今回は省略しますが. これらは定期的にテストを人間に代わって実行してくれます.
Push や PullRequest , 時間指定などの設定したトリガーでテストを実行してくれます.

CI ツールはテストのトリガーを引くだけではなくて, テスト環境を構築したりテスト結果を分析したりといったこともできるんですが, 今はとりあえずこんな役割なんだ~くらい知っていれば十分かなと思います.

まとめ

  • CI/CD は自動化されたテストを継続的に実施し. 自動的に各環境にサービスをデプロイし続ける.
  • 上記によって属人化を排除し, テストの再現性と精緻性が担保される上に高速に実施できる.
  • テストコードがないと何も始まらない

以上です. お付き合いくださりありがとうございました.
間違いや改善点はコメント頂けるととても嬉しいです.

今後もよろしくお願いします.

コメントの目的はコードの「意図」を理解してもらうこと

最近 Twitter 等でコメント論争がありましたので, 自分自身のコメント方の振り返りも兼ねて記事に起こしてみようと思いました.

本当は「コメントとはかく書くべきである!」みたいなことが言えればいいんですけど、私そこまでコメント力無いので基本的には『リーダブルコード』に沿って行く方針にしようかと思います.
『リーダブルコード』偉大ですからね.

需要があれば OSS とか使って『リーダブルコード』の読書会とか開催したいです.

本記事の目的は「コメントすべきこと」について一定の方針を打ち出すことです.


コメントはなぜ書くの

そもそもですがなぜコメントを書くのでしょうか?

コードの動作を説明するためであったり, オプションなどの Usage を知らせるためだったり, パブリックメソッドの使用条件や注意書きを知らせるためだったりするでしょう。

リーダブルコード的コメントの目的は以下の通り.

コメントの目的は、書き手の意図を読み手に知らせることである。(P.56)

つまりさっき上げたような目的は必要条件ではあるけれど十分条件ではないということです。

なぜ意図を知らせる必要があるのかについてもう少し掘り下げて考えてみます.
「意図を読み手に知らせる」ことは自分サイドの理由です. 立場を変えて私が読み手だった場合どういうことをコメントから読み取りたいんでしょう?

チーム開発の場合だと一見不必要な処理が行われていたりする時に「なんでこんな面倒なことしてんの???」となることがあります.
OSS を使う場合とかだと「どういう引数与えると意図した動きができるのか」や, 「特定の条件で使用すると著しくパフォーマンス落ちるよ」というワーニングが欲しかったりしますね.

コメントはフリースタイルなのでかくあるべき論は難しいですが. 章題に私なりに答えるとすると 読み手に納得感を与え, かつ書き手の想定通りの動きをしてもらうため になります.

コメントに書くべきでないこと

リーダブルコードでは下記3点についてコメントすべきではないこととされている.

  • コードからすぐにわかること
  • コメントのためのコメント
  • ひどい名前をサポートするためのコメント

コードからすぐにわかること

これはいわずもがなかと思います. 新しい情報を提供せず, 概要を表して理解の助けになるわけでもない価値のないコメントです.

// Car クラスの定義
class Car():
    // profit に値を設定する
    def setProfit():
        ...

コードからすぐにわかることをコメントに書かない(P.58)

コメントのためのコメント

これはもう説明するまでもないですね. SSIA とかって言いたい気持ちになります.

ひどい名前をサポートするためのコメント

関数名や変数名は自己文書化されているべきであり, その振る舞いはコメントで補助されるべきではありません. なんだか英語を翻訳した日本語みたいになりましたが, これ結構大事です.

ネーミングについて詳しいことはこの記事の中で議論することはしませんが, 例えば init_object という関数があったとしましょう. 一般的にこの関数に期待する振る舞いは, とあるオブジェクトの状態を初期化するということです. しかし下記のようにコメントされていたら。。。

# 新規に object を new して, 引数をメンバに割り当てる
def init_object():
    ...

おいおい, 確かに init してるけれどもさ... 違うやん...

この場合 init は厳密ではなく. 実質このコメントに表されている振る舞いは create です.

通常このような補助的コメントは書くべきではなく, 自己文書化された名前をつけるように工夫すべきです.

コメントに何を書くか

ざっくりいうと書き手の意図を読み手に知らせるために必要なことすべてです.
そのために何が必要なのかということなのですが, 正直筆者には明言できません.

言えることは書き手の意図を読み手に知らせる事はコメントに書くべきでないことに書いたこと以外は全て書くべきですが, 分かりやすさなどは個人によって基準が異なります.
無駄なものは逆に読みにくくするので書くべきでないことをまず把握すべきですし, 逆にそれ以外のものは書いた方がよいコメントだと言えるのではないでしょうか.

自分のコメントスタイルを見つけてみてそこから試行錯誤してみて, できる限り色々な人に意見を求めるのがよいと思います.

「とりあえず実装したけど. もっとパフォーマンスの良い実装があるはずなので高速化を行う」であったり, 「とりあえず import できる形式として csv だけ作ったけどその内 xlsx 形式に対応して欲しいって言われるだろうから要対応」をコメントしておくのも他者が見た際にそのためにこんな実装になっているのかということを理解してくれるでしょう.

具体的な話にはあまり言及しませんでしたが, 特にチーム開発においてはコメントも一種のドキュメンテーションなので, チームのルールを統一することが大事かと思います.

まとめ

何が言いたかったのかというと, コードにはコードの役割があって, コメントにはコメントの役割があることです.
コードで全てを表現することはほぼ無理だと思いますし. よしんば可能だったとしてもそのために 100 のコストがかかるのであれば 10 のコストでコメントに残すべきです. 読み手が理解しやすいコードを書くことがなによりも重要ですし, そのために上手にコメントを活用していきましょう.

ちなみに python には docstring という機能もあるので活用するとコメントが書きやすくなります.

お付き合いいただきありがとうございました.
間違いのご指摘やご意見等ございましたら気軽にコメントください.

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

メソッド名は抽象的でよいと思う

最近見たコードで「ん?これは。。。」というコードを見かけたので、そこら辺にスポットした話を書こうと思います。

まずは何も言わずに下記のコードを見てください。

class CsvGenerator():
    def generate_csv():
        xxx

    def generate_csv_from_json():
        yyy

    def generate_csv_with_header():
        zzz

賢明な方は即座にお気づきかと思いますが、3点ほど気になった箇所がありますよね。

  • メソッド名の中で CSV が重複してる
  • with_header はオプションなので引数で外部から制御したい
  • public メソッドに切りすぎ
  • メソッド名が具体的過ぎて拡張できない

4個になってしまった、、、

まぁ違和感としては3箇所なんですが、具体化すると4点ありました。

最後2点に関してはタイトル通りなんですが、具体的過ぎて結局実装者もその使用者も「やりたいことがこのメソッドではできないじゃないか、きーっ」ってなってしまうので、問題点としては抽象度がなさすぎるということですかね。

なので、再度まとめると

  • メソッド名の中で CSV が重複してる
  • with_header はオプションなので引数で外部から制御したい
  • 抽象度がなさすぎる

ということが問題かなと思います。

では1点ずつ見ていきましょう。

メソッド名の中で CSV が重複してる

やっぱり気になるのはこれでしょう。 CsvGenerator なので, 最終的に出力されるものが CSV であることは当然ですよね。

いや、おれは k8s を使うからどうしても yaml を出力したいんだよぉ!!っていう方はポリモーフィズム使ってもう一段階抽象度の高いクラスを作って OOP しようよって話でしょうか。

SubDbMigtartor で execute すればサブ DB を migrate するだろうことは共通認識だろうし、FileReader なら read すればファイルの中身を返してくれるだろうことは炊飯器のスイッチを押せばご飯が炊けるくらい当たり前のことですよね。

基本的にはクラスを作成する際にはひとつのことだけうまくやるように作ることが原則です。 今回の例でいえば CsvGenerator は csv ファイルを生成することだけできれば構わないので、メソッド名としては generate でよいのではないでしょうか。

とはいえ、おれは一つの炊飯器でもっちりも早炊きもできないと嫌なんだよ!!!という人も少なくはないでしょう。 詳しくは次のトピックで書きますが、とはいえ炊飯器はご飯を炊くことに特化した機械であり、期待する能力は炊飯であるということですね。

最近の炊飯器はパンも作れたりしますけどね。。。

with_header はオプションなので引数で外部から制御したい

要はご飯を炊くオプションとして早炊きやらもっちりだきやらを指定したいということですね。

先の書き方ではどうでしょうか。 ヘッダーのある CSV と無い CSV の2パターンを出力したいということです。

2 パターン程度であれば外部からの制御でなくともよいかも知れませんが、これが4つ5つとなってくるとどうでしょうか。

def with_header()

def with_index()

def without_blank_line()

なんて考えたら実装するのも大変ですが、使用者からしたらたまったもんではないですね。 「ヘッダーは欲しいけど空白行は詰めてある CSV が欲しい」なんて可能性もあります。

なのでこのような制御の利かないメソッドの一番の問題は条件に対して組み合わせができないことです。

外観がボタンで埋め尽くされた炊飯器なんて使いたくないですよね。。。

ではどうするか。

一般的なコードであればこのようになります。

def generate(filename. header = false, index = false. blank_line = false):
    xxx
    return csv_text

def _add_header():

def _remove_blank_line():

def _add_index():

コマンドラインから直接呼ばれるようなものであれば、argparse モジュールのようなもので外部からオプションとして渡せるようにするのがよいと思います。

もちろん抽象化することで直感的な使用はできなくなってしまいますが、この CsvGenerator の使用者の自由度は格段に上がったと思いませんか?

ライブラリを作成するにあたっては OSS や社内 PJ 、個人開発を問わずに非開発者が使いやすい構成になっているかということは非常に重要です。 最悪内部実装が間に合っていなくてもインターフェースの整理は優先的にやった方がよいと思います。 具体的な内部実装は private メソッドにやってもらいましょう。

抽象度がなさすぎる

さて、最後にタイトルを回収させてください。 実装レベルでの修正は先の2トピックでほぼ行いましたので、どちらかというと抽象的な話になります。

クラス名やメソッド名についてどうとらえてますか?

クラス名やメソッド名は役割であり、責務を表すものだと思っています。 なのでクラス名やメソッド名が上手くつけられない場合は責任が広がりすぎていることが多いです。 人間はあまり抽象化が得意な生き物ではありませんので、良く抽象化された名前を考えるよりも名前を付けやすいところまで責任を分割してしまう方が楽です。

開発者目線だけでなく、使用者の目線で考えることも抽象化には有効です。 例えば内部処理の例ですが

def exist_file(path):
    return path が存在するか判定

def is_csv_file(path):
    return path が存在するか判定

raise Exception if exist_file or is_csv_file

という場合と

def shold_throw_exception(path):
    return __exist_file() or __is_csv_file()

raise Exeption if shold_throw_exception()

ではどちらが可読性がありそうでしょうか? 後者では shold_throw_exception という関数に例外を投げるための条件がまとめられているため、ファイルが無い場合と CSV じゃなかった場合に例外を投げることが明示できます。 例外を投げる条件が増えても対応しやすいですね。

多くの場合他人が書いたコードをについて注意深く参照するのはクラス名とメソッド名、その引数と戻り値くらいじゃないですか?

CsvGenerator.generate('foo.xlsx') を実行した場合、内部で yaml に変換されてようが Json を経由していようとしったこっちゃないわけですね。 generate が csv の文字列を返して、generate_file で csv ファイルのパスとかファイルオブジェクトを返すくらいの柔軟性は期待の範囲内でしょう。

もちろん動作速度を高速なものを探す場合などは別の話ですが。。。

まとめ

クラス名が明示的であれば public メソッドは抽象的な方がよい

クラス名やメソッド名は役割であり、責務を表すもの

という上記2点だけお伝えできればと思います。

今後より内容の濃いものを分かりやすく簡潔に伝えられるように精進しますので、応援よろしくお願い致します。 内容に関して修正点や意見がございましたらコメントいただけますととても嬉しいです