BOOLパラメータの代わりに強い型を使用する


コードレビューでいくつかの定期的なテーマがあります.経験豊かなレビュアーは、しばしばそのような繰り返されるパターンのどこかでコメントのテンプレートを持っています.時々彼らの心の後ろだけで、しかし、しばしばどこかで書かれます.おそらく、彼らはまた、参照するいくつかの参考資料を持っているcrucial parts of good code review comments .
参照を使用することによって、他の誰かに信頼性の質問を委任することができます.
私が実行するコードレビューのこれらの定期的なテーマのうちの1つは、我々がbool 関数パラメータとしてsを指定します.そして、私が使用する参考資料は、マットGodboltによって示される会議話です.

Booleanパラメーターを使用すると、コードを理解することが困難になります


それはとても簡単な紹介ですbool 関数パラメータ.あなたは、それが安いが、十分に良い解決であると思うかもしれません.あなたが車を拾うかドロップする場所を取得しなければならないレンタカーシステム上で動作することを想像してください.
まず、都市コードと会社コードをパラメータとして場所を識別するための単純な関数を書き、また、戻りたい場所が通過したい時点で開いていることを確認するための日付を書きます.
std::vector<Location> searchLocation(const std::string& iCityCode, const std::string& iCompanyCode, const Date& iDate);
後に、少なくとも場所の特定の時点で-あなたは車を拾うことはできませんが、それをドロップすることができます場所があることを把握する.
あなたsearchLocation 関数は、ピックアップまたはドロップポイントを探すかどうかを考慮する必要があります.
どうやってやるの?
あまりにも頻繁に選択された実装は、このパラメータをBooleanとして取ることです.
std::vector<Location> searchLocation(const std::string& iCityCode, const std::string& iCompanyCode, const Date& iDate, bool iPickupOrDropoff);
今何が問題ですか.
署名は特に読めない.
実際には、ほとんどの問題bool パラメータはシグネチャにない.さて、以下のような呼び出しを見始めましょう.
auto locations = searchLocation(aCityCode, aCompanyCode, false);

// or 

auto locations = searchLocation(aCityCode, aCompanyCode, true);
の定義を見ずにsearchLocation その第三の引数は何か分からないbool を表します.
あなたは、状況がよりよくなることをすることができると主張するかもしれません.戻り値を格納するローカル変数の名前を変更できますlocationspickupLocations or dropoffLocations . 確かに、あなたはそれを知っていることを意味するbool の略?
いいえ、あなたはそれを知らない.あなたが考えるならば、あなたはそれを仮定することができます.
しかし、誰が仮定したいですか?
そして、より多くのブールパラメータを加えるならば、どうですか?
誰かが機能が障害者のためのアクセス可能な場所を検索することができることを実現します.
auto locations = searchLocation(aCityCode, aCompanyCode, false, true);
もちろん、より多くのブールパラメータを追加し続けることができますが、ここで止まりましょう.
まじめに.
これらのbooleansが意味する地獄?どうすればfalse またはtrue ? あなたは署名にジャンプすることなく知らない.近代的なIDEは、ツールの機能の署名を示すツールのヒントを使用することができますが、-可能な場合、我々は、特にこのようなツールの種類はまだ他の人気の言語のレベルではないC++のためにそれを取ることはできません.
しかし、読みやすさに問題はない.互いの隣に同じタイプの複数のパラメータで、我々は単に値を混ぜることもできます.

別の解決策


私は実際には最初の1つではないだろうが、私はあなたに3つの異なる解決策を示すつもりです.それは、より多くの反解決です.

コードコメントの追加は長い実行ではない


状況をよりよくする非常に単純な方法は、若干のコメントをすることです.
auto locations = searchLocation(aCityCode, aCompanyCode, false /* iPickupDropoff */, true /* iAccessible */);
さらに良い行を破る場合!
auto locations = searchLocation(aCityCode, 
                                aCompanyCode,
                                false /* iPickupDropoff */,
                                true /* iAccessible */);
少なくとも、各引数は何を意味知っている.
それでも、あなたにはコメントが正しいという保証がありません、そして、それは彼らを加えるか、維持する余分の努力です.誰かがそのようなコメントを追加することを忘れたり、物事が変更されたときに、彼らは無効になるかもしれないが、誰もそれらを更新します.
加えてfalse /* iPickupDropoff */ まだ明確に通信するかどうかfalse ピックアップまたはドロップオフの場所の意味があります.

強力なタイプを使用して、いくつかのenumを紹介!


本当の解決策は、ブール人をenum sを宣言しましょうenum それぞれの代わりにbool パラメータ!
enum class LocationType {
  Pickup, Dropoff
};

enum class Accessible {
  Yes,
  No,
};
彼らは、ベース・タイプのbool , しかし、後であなたがより多くのタイプを加えたいと思うならば、どうですか?私は道を開いていた.
今、あなたはこれらのenumsを使用する関数と関数呼び出しを更新することができます.
std::vector<Location> searchLocation(const std::string& iCityCode, const std::string& iCompanyCode, const Date& iDate, LocationType iLocationType, Accessible isAccessible);

// ...
auto locations = searchLocation(aCityCode, aCompanyCode, LocationType::Pickup, Accessible::Yes);

// ...

auto locations = searchLocation(aCityCode, aCompanyCode, LocationType::Dropoff, Accessible::No);
これらの呼び出しを読むとき、あなたには疑いがありません.少なくとも最後の2つの引数のためではありません.あなたの意図はもっときれいに表現できなかった.
分岐を使用する場合は、関数定義ではif (iLocationType) { /* ... */ } , あなたは、可能な限りそれを比較する必要がありますenumif (iLocationType == LocationType::Pickup) { /* ... */ } . これは有利だと思う.何が起こっているかについて何の質問もしないのはとても明白です.
フリップ側は、より多くの機能の定義だけでなく、実際にどこに入力する必要があります.しかし、それは可読性の利得のための公正な価格であるので、保全性の利得であると思います.

それらの余分なパラメータの必要性を取り除く


これらの余分なパラメータの必要性を削除することができれば?
機能を持っている代わりにbool ピックアップまたはdropoffの場所を検索するかどうかを示すパラメーターは、適切な名前を持つ2つの関数を持つことができます.

std::vector<Location> searchLocation(const std::string& iCityCode, const std::string& iCompanyCode, const Date& iDate, bool iPickupOrDropoff);

// vs

std::vector<Location> searchPickupLocation(const std::string& iCityCode, const std::string& iCompanyCode, const Date& iDate);

std::vector<Location> searchDropoffLocation(const std::string& iCityCode, const std::string& iCompanyCode, const Date& iDate);

このソリューションは非常に読みやすいですが、それだけで行く.それは私たちに2つの問題を残します.
複数のブールパラメータがあるとき、あなたは何をしますか?このテクニックにしたがっているなら、あなたのAPIは指数関数的に成長します.
また、実装で何をするのですか?コードを複製しますか.Aを使用しますかprivate を取る共通関数bool ? または、基本クラスが共通のコードを含むクラス階層になり、派生クラスがカスタマイズポイントを提供します.
後者はほとんどの状況で過酷すぎるようです.
ブールパラメータに基づく内部インターフェースを使用することは、外部APIでそれを使用するより非常によくありません.メンテーナを尊敬すべきだ.あなたはそれらのために簡単にコードを理解する必要があります.
With bool それは可能ではない.最後に、おそらくいくつかを使用する必要がありますenum s.

結論


この記事では、我々の機能の署名にどのように不要なBooleansが表示され、どのように我々のAPIの理解と保守性を減らすことができます見た.
我々は状況をより良くする2つの方法について話し合った.彼らがAPIの指数関数的成長につながることができるので、差別化実装とより良い名前を使用することは通常長期的な解決でありません、しかし、彼らは特定の状況で十分によいです.
さもなければ、この場合、強いタイプを導入することができます.enum 判読不能を取り除くためにbool sとすべてのために読みやすさを改善します.
他のいくつかのアプローチや意見についてはC++ Stories

接続深い


この記事が好きなら
  • ボタンのようにヒット

  • subscribe to my newsletter
  • そして、接続しましょう!