
私の
最後の投稿は大きな共鳴を引き起こしました。 多くのコメントはありませんでしたが、私は多くの手紙を受け取り、何人かの読者は
オープンな声明を出しました (ただし、私自身とハブルに対する攻撃は全体的に勝っていますが、問題のメリットについての考えがあります)。 したがって、「
有名な会社の質問についての私の考え」というジャンルで書き続けることにしました。 この投稿では、2つの問題を解決しようとします。(i)前の投稿の読者の質問や異議に答える、(ii)ある意味で、IFIFフリープログラミングの哲学的思考を推進する。 かなりの数の手紙がありますが、投稿の1つだけに興味がある人は半分をスキップできます。
そしてもう1つ、このトピック(および最後のトピック)は誰とも衝突しません。 興味深い問題を推測するのは興味深いことです。 サブテキスト、ヒント、呼び出しはありません。 陰謀説の妄想と支持者にリラックスしてもらいます。
今回は質問
4を見たいと思います。
これが彼のコードです。 彼がしようとしていたように、少しcombいた
#include <string> #include <stdexcept> #include <iostream> class CodeGenerator { public: enum Lang {JAVA, C_PLUS_PLUS, PHP}; CodeGenerator(Lang language) { _language=language; } std::string generateCode() { switch(_language) { case JAVA: return std::string("Java: System.out.println(\"Hello world!\");"); case C_PLUS_PLUS: return std::string("C++: std::cout << \"Hello world!\";"); } throw new std::logic_error("Bad language"); } std::string someCodeRelatedThing() // used in generateCode() { switch(_language) { case JAVA: return std::string("http://www.java.com/"); case C_PLUS_PLUS: return std::string("http://www.cplusplus.com/doc/tutorial/"); } throw new std::logic_error("Bad language"); } private: Lang _language; }; int main() { CodeGenerator cg(CodeGenerator::JAVA); std::cout << cg.generateCode() << "\tMore info: " << cg.someCodeRelatedThing() << std::endl; CodeGenerator ec(CodeGenerator::PHP); try { ec.generateCode(); } catch (std::logic_error * e) { std::cout << "ERROR: " << e->what() << std::endl; } return 0; }
ここで何を取り除きたいかを概説しましょう:
- すべての言語をまとめたくはありません 。 それらを分解すると、保守、目での確認、テストが容易になります。
- 複数のスイッチを取り除きたいです。 それらはコードで混雑しており、新しい言語を追加するときはすべて監視する必要があります。 これは面倒です。 さらに、これらのスイッチは毎回実行され、クラスユーザーはそれについて何もできません。
- 私は_languageに直面して時限爆弾を取り除きたいです。 この素晴らしい変数は、ある場所で設定され、使用されます(そして例外をスローします!)別の場所で(またはむしろ他の場所で)。 これは確かにデバッグや障害の原因の発見を促進しません。
- これら2つのことがお互いの開発を妨げないように、インターフェイスを実装から分離したいと思います 。
- _languageの値の単一の「復号化」である早期エラー診断の実装をプログラマーに促すような設計にしたいと思います。 一般的に、賢明に行動します。 既存のソリューションでは、まったく逆の動作が推奨されます。
順次移動します。 多くの手紙が判明しましたが、それらはすべて単純であり、過度の脳の努力を必要としないことを願っています。 自分の好きな飲み物を注いで先に進みます。
分割と支配
まず、個別の言語に関係するすべてを個別のクラスに配布します。 新しいことは何もしませんでしたが、開発とテストが少し簡単になりました。
#include <string> #include <stdexcept> #include <iostream> class CodeGeneratorJavaProcessor { public: std::string code() { return std::string("Java: System.out.println(\"Hello world!\");"); } std::string thing() { return std::string("http://www.java.com/"); } }; class CodeGeneratorCppProcessor { public: std::string code() { return std::string("C++: std::cout << \"Hello world!\";"); } std::string thing() { return std::string("http://www.cplusplus.com/doc/tutorial/"); } }; class CodeGenerator { public: enum Lang {JAVA, C_PLUS_PLUS, PHP}; CodeGenerator(Lang language) { _language=language; } std::string generateCode() { switch(_language) { case JAVA: return CodeGeneratorJavaProcessor().code(); case C_PLUS_PLUS: return CodeGeneratorCppProcessor().code(); } throw new std::logic_error("Bad language"); } std::string someCodeRelatedThing() { switch(_language) { case JAVA: return CodeGeneratorJavaProcessor().thing(); case C_PLUS_PLUS: return CodeGeneratorCppProcessor().thing(); } throw new std::logic_error("Bad language"); } private: Lang _language; }; int main() { CodeGenerator cg(CodeGenerator::JAVA); std::cout << cg.generateCode() << "\tMore info: " << cg.someCodeRelatedThing() << std::endl; CodeGenerator ec(CodeGenerator::PHP); try { ec.generateCode(); } catch (std::logic_error * e) { std::cout << "ERROR: " << e->what() << std::endl; } return 0; }
この純粋な機械的改善については議論しません。
統合インターフェイスと複数のスイッチの障害
すべての言語に対して単一のインターフェースを作成しましょう。 これで、言語コードではなく、その言語のジェネレータークラスへのポインターをCodeGeneratorに保存できます。 言語コードからクラスへの変換は1回発生します。
#include <string> #include <stdexcept> #include <iostream> class CodeGeneratorAbstractProcessor { public: virtual std::string code() = 0; virtual std::string thing() = 0; virtual ~CodeGeneratorAbstractProcessor() {} }; class CodeGeneratorJavaProcessor : public CodeGeneratorAbstractProcessor { public: std::string code() { return std::string("Java: System.out.println(\"Hello world!\");"); } std::string thing() { return std::string("http://www.java.com/"); } }; class CodeGeneratorCppProcessor : public CodeGeneratorAbstractProcessor { public: std::string code() { return std::string("C++: std::cout << \"Hello world!\";"); } std::string thing() { return std::string("http://www.cplusplus.com/doc/tutorial/"); } }; class CodeGeneratorBadProcessor : public CodeGeneratorAbstractProcessor { public: std::string code() { throw new std::logic_error("Bad language"); return std::string(); } std::string thing() { throw new std::logic_error("Bad language"); return std::string(); } }; class CodeGenerator { public: enum Lang {JAVA, C_PLUS_PLUS, PHP}; CodeGenerator(Lang language) : processor(0) { switch(language) { case JAVA: processor = new CodeGeneratorJavaProcessor(); break; case C_PLUS_PLUS: processor = new CodeGeneratorCppProcessor(); break; case PHP: processor = new CodeGeneratorBadProcessor(); break; } } ~CodeGenerator() { delete processor; } std::string generateCode() { return processor->code(); } std::string someCodeRelatedThing() { return processor->thing(); } private: CodeGeneratorAbstractProcessor * processor; }; int main() { CodeGenerator cg(CodeGenerator::JAVA); std::cout << cg.generateCode() << "\tMore info: " << cg.someCodeRelatedThing() << std::endl; CodeGenerator ec(CodeGenerator::PHP); try { ec.generateCode(); } catch (std::logic_error * e) { std::cout << "ERROR: " << e->what() << std::endl; } return 0; }
私が理解しているように、前のメモへの
応答で言及されたのは、まさにそのようなデザインについてでした。 著者は、他の何かが改善されることを疑った。 さて、見てみましょう。
しかし、物事の見方を変えませんか?
CodeGeneratorの現在の動作を見てみましょう。 ただ二つのこと:
- コンストラクターで列挙型をオブジェクトに変換します
- すべての作業を行うオブジェクトのラッパーを提供します
そのようなクラスが必要なのでしょうか? この質問を今のところ開いたままにして、このクラスがどのように使用されるかを見てみましょう。 明らかに、このようなもの:
void i_like_to_convert_enum_to_lang(CodeGenerator::Lang lang_code) { CodeGenerator cg(lang_code); std::cout << cg.generateCode() << "\tMore info: " << cg.someCodeRelatedThing() << std::endl; }
つまり、enumを一生ドラッグして、突然それを必要とするアルゴリズムに変換することを前提としています。 すべてのオーバーヘッド:オブジェクトの作成/削除、enumの正確性のチェック、例外の処理... enumを一度オブジェクトに変換して、それを使い続けるのが賢明でしょうか? これは多くの問題を解決し、多くの利点をもたらします。 単一のパススルーオブジェクトがその作業に関する統計情報を収集し、何かをキャッシュできるとしましょう。このソリューションはどうですか。
void i_like_to_use_generators(CodeGeneratorAbstractProcessor * cg) { std::cout << cg->code() << "\tMore info: " << cg->thing() << std::endl; }
ここでは、CodeGeneratorを本当に放棄しました。
もちろん、このような決定的なステップは、インターフェースを変更せずに行うことはできませんでしたが、どれだけ多くの利点が得られたのでしょう!
ちなみに(前の記事の説明に戻ります)、_ language爆弾とプロセッサー爆弾を放棄するとすぐに、そのようなものではありませんでしたが、ジェネレーターの作成と削除の操作が互いに非常に近くなり、オブジェクトの多態性除去の必要性がなくなったことに注意してください。
そして、今こそ脱線を食い止める時です。
<歌詞の余談>あらゆる種類のHaskelsになりがちなプログラマーの間では、ifや他のブランチに対する戦闘機の好奇心が強い動きがあります。
ifsの問題は、感染のように増殖することです。 実生活で非常に頻繁に見られる例を挙げます。 デバッグを有効にする機能を備えたプログラムを作成するとします。
多くの場合、これは次のように行われます。
#include <iostream> #include <unistd.h> int main(int argc, char *argv[]) { // ( ) bool debug(false); int r; while ((r = getopt(argc, argv, "d")) != -1) { switch ( r ){ case 'd': debug = true; break; } } // , // // ( ) if (debug) { std::cout << "Some debugging" << std::endl; } return 0; }
このようなコードの例は、ソースcdrtools、Python、git、cmake、m4(デバッグ変数)、firefox(debug_mode)、mpalyer / mencoder(debugタイプのフィールド、さまざまな構造のb_debug)、x11vnc(debug / crash_debug / debug_pointer)にあります...お気に入りのプログラムのソースコードを見ると、追加の図があります。
問題は、最初のifが1つの論理値を別の論理値に変換することです。 そして、メインコードでは、この2番目の論理値が毎回解釈されます。
もちろん、これはパフォーマンスの低下につながりません(逆に、これはパフォーマンスの点で非常に優れたアプローチです)が、コードを乱雑にし、そのサポートを非常に複雑にします。 このすべてのコードでミスを犯すのは簡単です。 この一連のコードは変更が困難です。
しかし、幸いなことに、別の解決策があります。 そのようなタイプ:
#include <iostream> #include <unistd.h> class noout : public std::ostream {}; noout& operator<< (noout& out, const char* s) { return out; } int main(int argc, char *argv[]) { // // noout e; std::ostream *debug(&e); int r; while ((r = getopt(argc, argv, "d")) != -1) { switch ( r ){ case 'd': debug = &std::cout; break; } } // *debug << "Some debugging" << std::endl; return 0; }
ここでは、デバッグメッセージの処理方法を決定するオブジェクトへの変換がすぐに実行されます。
プログラムの本体にブランチは不要になりました。
さらに、このコードの開発は非常に簡単になりました。 たとえば、デバッグ情報を指示する新しいストリームを追加できます。
#include <iostream> #include <unistd.h> class noout : public std::ostream {}; noout& operator<< (noout& out, const char* s) { return out; } int main(int argc, char *argv[]) { // // noout e; std::ostream *debug(&e); int r; while ((r = getopt(argc, argv, "de")) != -1) { switch ( r ){ case 'd': debug = &std::cout; break; case 'e': debug = &std::cerr; break; } } // // , *debug << "Some debugging" << std::endl; return 0; }
もちろん、この実装はアプローチのみを示しています。 もちろん追加するのを忘れました
noout& operator<< (noout& out, const signed char* s); noout& operator<< (noout& out, const unsigned char* s); ...
そして、はるかに。 もちろん、ロギングに本格的な手段を使用する方が正しいでしょう。 これはすべて明らかです。 しかし、私はアイデア自体を説明したいと思います。
そして、注意してください、ここではオブジェクトはポリモーフィックな生活を送っていますが、ポリモーフィックな削除はまだ必要ありません(これは前のメモに戻ります)。
</叙情的な余談>インターフェースを実装から分離する
CodeGeneratorAbstractProcessorクラスを少し作り直し、同時にNVIの概念を示します。 実装がインターフェースからどれほどうまく分離されているかを見てください:
#include <string> #include <stdexcept> #include <iostream> class CodeGeneratorAbstractProcessor { public: std::string generateCode() { return code(); } std::string someCodeRelatedThing() { return thing(); } protected: ~CodeGeneratorAbstractProcessor() {} private: virtual std::string code() = 0; virtual std::string thing() = 0; }; class CodeGeneratorJavaProcessor : public CodeGeneratorAbstractProcessor { public: std::string code() { return std::string("Java: System.out.println(\"Hello world!\");"); } std::string thing() { return std::string("http://www.java.com/"); } }; class CodeGeneratorCppProcessor : public CodeGeneratorAbstractProcessor { public: std::string code() { return std::string("C++: std::cout << \"Hello world!\";"); } std::string thing() { return std::string("http://www.cplusplus.com/doc/tutorial/"); } }; class CodeGeneratorBadProcessor : public CodeGeneratorAbstractProcessor { public: std::string code() { throw new std::logic_error("Bad language"); return std::string(); } std::string thing() { throw new std::logic_error("Bad language"); return std::string(); } }; void i_like_to_use_generators(CodeGeneratorAbstractProcessor * cg) { std::cout << cg->generateCode() << "\tMore info: " << cg->someCodeRelatedThing() << std::endl; } int main() { CodeGeneratorJavaProcessor java; i_like_to_use_generators(&java); return 0; }
パブリックメソッドはインターフェイスを実装します。 プライベートメソッドと仮想メソッドを使用して、クラスの動作を設定します。 これら2つのことで、簡単かつ自然にジャグリングできるようになりました。 両方を最適化します。 たとえば、次のように:
#include <string> #include <stdexcept> #include <iostream> class CodeGeneratorAbstractProcessor { public: std::string generateCode() { return lang() + ": " + code(); } std::string someCodeRelatedThing() { return thing(); } protected: ~CodeGeneratorAbstractProcessor() {} private: virtual std::string lang() = 0; virtual std::string code() = 0; virtual std::string thing() = 0; }; class CodeGeneratorJavaProcessor : public CodeGeneratorAbstractProcessor { public: std::string lang() { return std::string("Java"); } std::string code() { return std::string("System.out.println(\"Hello world!\");"); } std::string thing() { return std::string("http://www.java.com/"); } }; class CodeGeneratorCppProcessor : public CodeGeneratorAbstractProcessor { public: std::string lang() { return std::string("C++"); } std::string code() { return std::string("std::cout << \"Hello world!\";"); } std::string thing() { return std::string("http://www.cplusplus.com/doc/tutorial/"); } }; class CodeGeneratorBadProcessor : public CodeGeneratorAbstractProcessor { public: std::string lang() { throw new std::logic_error("Bad language"); return std::string("???"); } std::string code() { throw new std::logic_error("Bad language"); return std::string(); } std::string thing() { throw new std::logic_error("Bad language"); return std::string(); } }; void i_like_to_use_generators(CodeGeneratorAbstractProcessor * cg) { std::cout << cg->generateCode() << "\tMore info: " << cg->someCodeRelatedThing() << std::endl; } int main() { CodeGeneratorJavaProcessor java; i_like_to_use_generators(&java); return 0; }
また、開発者のユーザーインターフェイスと「インターフェイス」は、一貫性がなく、便利で、論理的ではなく、コンパクトになりました。 そして今、クラス自体は、すべての人に特定の規律と構造を指示しています。
したがって、私たちは記事の冒頭で述べたすべての願いを実現しました。 もう停止する時間です。 最後まで読んだすべての人への帽子を脱ぎます。 ありがとう
写真はドルメンを示していますが、これは記事とは関係ありません。 このトピックに関する記事を書きたかったのは彼らだけでした。