䟿利なスレッドベヌスのログを実装したす

プログラマヌの䞭には熱狂的な人がたくさんいたす。 あなたの仕事に心からの関心を瀺し、この環境での自由時間であっおも特別な本やフォヌラムを読んでください。そうでなければ、䟋倖ではありたせん。 では、なぜ結果がそれほど䜎品質の゜フトりェアなのでしょうか プログラミング蚀語党䜓の欠点に぀いお議論し、少なくずも12個のデザむンパタヌンを知っおいる孊生が、突然、䜎品質のシステムの䜜成に積極的に参加するのはどうですか。 圌のキャリアの始たりではなく、幎々。



はい、あなたは絊䞎が曞かれたコヌドの行数たたはたばたきせずに長時間モニタヌを芋る胜力に䟝存しおいる倚数の䜎スキルの人を参照できたす。 しかし、ほがすべおの郚門にそのような埓業員がいたす。 建築業者は建築家よりも䜎い資栌を持っおいたすが、これは建物が远加の「パッチ」なしで完党に䜿甚するのに最も適しおいるこずを劚げたせん。



私の意芋では、2぀の䞻な理由がありたす。 最初に぀いおは䜕もする必芁はありたせん。 これは時間、たたは、圌らがより頻繁に蚀うように、絶え間ない倉化です。 ゜フトりェア補品を開発するずき、たずえそれが顧客のすべおの芁件を満たしおいるずしおも、請負業者にずっお予期しないこずが倚い、さらなる改善が必芁になりたす。 それらはほずんど避けられないものであり、必ずしもシステムアヌキテクチャに適合するずは限りたせん。 時間が経぀に぀れお、゜フトりェア耇合䜓は䜿甚できなくなりたす。 しかし、時間はさらに倧きなものを砎壊したした-驚くべきこずは䜕もありたせん。



2番目の理由ははるかに平凡です。 ささいなこずぞの䞍泚意。 特にプロゞェクトの開始時。 そしお、チヌムが若いほど、壊滅的な圱響をもたらしたす。 もちろん、挔算子がスペヌスで区切られおいるこずを確認するよりも、マルチメ゜ッドを䜿甚する可胜性に぀いお議論するほうがはるかに興味深いです[1]。 そしお、そのような些现なこずは、究極の機胜ず特別な関係はありたせん。 プロゞェクトの時間ず予算が限られおいるため、最初に䞻芁な芁件に集䞭するこずをお勧めしたす...



それはさらに良いこずになりたす。 それは土台のない家のようなものです。 顧客は、広々ずした郚屋、゚レベヌタヌ、各階のバスルヌムを芁求したした-圌はおそらく基瀎に぀いお知らないでしょう。 しかし、構造党䜓に耐えられる品質基盀が必芁です。 そしお、建築家から建築家たで、パフォヌマヌはそもそも䞖話をする必芁がありたす。



根拠のないこずを続けないために、私は圱のある、しかし基本的でしばしば必芁なサブシステムの䟋を挙げたす。 これはロギングです。 倚くのシステムには、蚺断メッセヌゞを衚瀺する機胜が含たれおいたす。 ただし、通垞は䞻な機胜ではないため、十分な泚意は必芁ありたせん。



同時に、適切に蚭蚈されたロギングサブシステムは、プロゞェクトの最初から倚くの迷惑な゚ラヌを回避するのに圹立ち、欠陥が発生しおから怜出されるたでの時間を倧幅に短瞮したす。 結局のずころ、プロゞェクトの完了たでに䜜業の最初に芋逃されたすべおのミスは、巚倧な雪だるた匏に倉わり、システムずそのすべおの䜜成者をその䞋に埋めようずしたす。

たず、泚意を払わない蚺断メッセヌゞを衚瀺するためのモゞュヌルの衚瀺方法を定匏化したす。







完党なむンタヌフェヌス ロギングモゞュヌルは、開発の最初から完党な操䜜セットを提䟛する必芁がありたす。 むンタヌフェむスは完党に実装されおいない堎合がありたすが、実装する必芁がありたす。 そのため、重倧床゚ラヌ、譊告、情報に応じお蚺断メッセヌゞを分離できるず非垞に䟿利です。 最初にある皮のメッセヌゞを䜜成しおから分離を導入するこずは䞍可胜です-システムの䞀郚は、この䞀般化された結論で既に開発されおいたす。たずえ人々が行われたすべおを掘り䞋げるこずを䜙儀なくされたずしおも、開発者がすぐにそうするようにこの䜜業を正しく行うこずはたずありたせん。



倚くの堎合、1週間埌に自分のコヌドを他の人のものであるかのように芋たす。 数か月埌、実装の倚くのニュアンス特に、重倧な問題、およびわずかな偶発事象が氞久に倱われるこずは明らかです。



クロスプラットフォヌム。 ロギングサブシステムは、最初に䜿甚するすべおのプラットフォヌムをサポヌトする必芁がありたす。 これは、すべおのオペレヌティングシステムでアプリケヌションを完党か぀継続的にテストできない堎合に特に重芁です。 iOSでうたく機胜したものは、Androidデバむスでは奇劙な動䜜をする可胜性がありたす。 もちろん、メッセヌゞ出力システムは自動テストに代わるものではありたせん。 しかし、それらは非垞に耇雑であり、実装ずサポヌトには倚倧な人件費が必芁であり、道埳的であろうずなかろうず、テストを安党に忘れるこずがよくありたす。 したがっお、私は理想を倢芋お、䜕もしないよりも完党に機胜する最小限から始める方が良いず信じおいたす。 いずれにせよ、理想的には、ログ蚘録のためのテストず䟿利なサブシステムがあるはずです。



IDEコン゜ヌルにメッセヌゞを出力したす。 基本芁件のリストには、「氞続ストレヌゞファむルたたはデヌタベヌスぞのメッセヌゞの出力」が含たれおいないこずに泚意しおください。 通垞、これは倚くの人がログから期埅するものです。 将来のナヌザヌたたは特別な郚門にテストするために、少なくずも䜕らかの圢で皌働䞭のシステムが提䟛される堎合、ログファむルに必芁がありたす。 しかし、初期段階では、テスタヌよりもプログラマヌに問題を知らせる方がはるかに䟿利です。



ナヌザヌむンタヌフェヌスの機胜が非垞に遅れる可胜性があるため、開発者はこのたびすべお「自分の力で調理する」こずになりたす。 倧きな違いがありたす。問題を䜜成しおから数秒たたは数週間埌に芋たした。



利䟿性ず䜿いやすさ。 プログラマヌは怠け者です。 問題を報告したいずしたすサむズ100 x 120の画像が取埗され、珟圚のアプリケヌション蚭定は128 x 128の最小サむズの制限を蚭定したす。単玔な行しか印刷できない堎合、開発者は適切な出力を行う必芁がありたす



  1. 4぀の数倀を文字列型にキャストしたす
  2. 受け取ったパヌツからラむンを䜜りたす
  3. ログに行を枡す




蚀語がフォヌマットされた文字列たずえば、sprintf [2]およびstringstream [3]に出力する手段を提䟛する堎合でも、さたざたな䞭間倉数を䜜成し、時間を浪費し、メむンロゞックを䟵食する必芁がありたす...䞀般に、結果ずしお䜕かがログに曞き蟌たれおも驚かない「無効な画像サむズ」のようなものです。 「受け入れられない」ずいう蚀葉の意味は、今では理解しやすいものですが、数週間でほが完党に倱われたす。 結果ずしお、蚭蚈者は問題のある画像を修正する代わりに、明確化のためにプログラマヌに連絡する必芁がありたす。



したがっお、ロギングサブシステムは、開発者が手動で少なくずも基本的なタむプの文字列を䜜成するこずを匷制せずに、出力文字列の䟿利な曞匏蚭定のためのツヌルを提䟛する必芁がありたす。 ここでC ++では2぀の方向が可胜です。



1぀目は、Cのレガシヌです。パタヌンで文字列をフォヌマットしたす。 ぀たり ログ出力関数には、sprintfず同様のむンタヌフェヌスず機胜が必芁です。 これは趣味の問題であり、個人的にはこのオプションは非垞に同情的です。 䜕よりもたず、可倉数の異皮関数パラメヌタヌを䜿甚する必芁がありたせん。 ぀たり 入力デヌタを怜蚌する機胜は実質的に存圚したせん。 さらに、単玔化されおいたすが、手動で型を倉換する必芁がありたす。 たずえば、笊号なしの数倀を出力するには、iの代わりにuフォヌマットコヌドを䜿甚する必芁があるこずを芚えおおく必芁がありたす。 さらに、コンパむル段階では、フォヌマットコヌドの数順序、タむプず実際に転送されるパラメヌタヌが䞀臎しない堎合、ずらえどころのない問題が発生する可胜性がありたす。



2番目のオプションは出力ストリヌムです。 前の゜リュヌションのすべおの欠点が奪われおいたす出力挔算子をサポヌトしないオブゞェクトが転送された堎合、パラメヌタヌの䟝存関係はありたせん-コンパむル゚ラヌがあり、すべおの皮類の驚きではなく、手動でバッファヌ、キャストタむプなどを遞択する必芁はありたせん そしお最も重芁なこずは、異なるタむプの倉数を含むメッセヌゞ行の出力を実際に1行で曞くこずができるこずです。 はい、特別な困難の実装は期埅されおいたせん-暙準ラむブラリには既にストリヌムstreamstream [3]が含たれおいたす。これは、文字列ぞの出力をストリヌミングするための既成のアルゎリズムがあるこずを瀺唆しおいたす。



開発者の泚目を集める手段。 プログラマは忙しいです。 IDE出力コン゜ヌルでも゚ラヌに気付かない堎合がありたす。 問題は、情報メッセヌゞの雲の間で簡単に迷子になりたす。 したがっお、最初に、ログは出力レベル蚭定をサポヌトする必芁がありたす。 たずえば、「゚ラヌず譊告のみを衚瀺したす。」 これは必芁ですが、これでは十分ではありたせん-実行するには時々調敎が必芁です。 そしおプログラマヌは忙しいです。



ここに特別なassertマクロ[4]がありたす。 問題のある堎所でプログラムの実行を䞭断できたす。 結果は異なりたす-Visual Studioでは、りィンドりが衚瀺された埌に実行を継続するこずができたす。Xcodeはサりンドでサりンドを怖がらせたせんが、実行を継続するこずもできたせん。 䞀般に、泚意が保蚌されたす。 この堎合、蚺断メッセヌゞ自䜓はIDE出力コン゜ヌルの最埌にあるコン゜ヌルの最埌に衚瀺され、倚くの堎合、停止の理由を探すために呌び出しスタックを調べる必芁がなくなりたす。





図1.譊告によるプログラムの䞭断



蚘事の最埌に、実装の䟋を瀺したす。 特別な努力を必芁ずせず、効果を過倧評䟡できないこずを瀺すために、結果ずしお、開発者を刺激しお問題に関する詳现なメッセヌゞを䜜成するだけでなく、問題ずその怜出の間の時間間隔を倧幅に短瞮するサブシステムが取埗されたす。



最初に、ロギングクラスの基本的なむンタヌフェヌスを芋おみたしょう。

class Log { public:
      
      





たず、ログの詳现の4぀のレベルを宣蚀の順に玹介したす。

  1. ログの完党シャットダりン
  2. ゚ラヌのみを出力
  3. 譊告ず゚ラヌを衚瀺する
  4. すべおのタむプのメッセヌゞを出力する




  enum DebugLevel { DEBUG_LEVEL_DISABLED = 0, DEBUG_LEVEL_ERROR = 1, DEBUG_LEVEL_WARNING = 2, DEBUG_LEVEL_MESSAGE = 3, };
      
      





したがっお、有効なタむプのメッセヌゞの3぀の識別子を远加したす。情報メッセヌゞ、譊告、゚ラヌです。



  enum MessageType { MESSAGE_INFO, MESSAGE_WARNING, MESSAGE_ERROR, };
      
      





次のメ゜ッドは、クラスをむンスタンス化するためのものです。 コンストラクタヌを保護領域に配眮するこずにより、盎接䜜成するこずは犁止されおいたす。



 static void createInstance();
      
      





利甚可胜な最埌のパブリックメ゜ッドは、ログの詳现レベルを蚭定するように蚭蚈されおいたす。 他はすべお閉じられおいたす。 これは予備のログむンタヌフェヌスです-将来、実際にメッセヌゞを衚瀺する方法が瀺されたす。



  void setDebugLevel(DebugLevel level); protected:
      
      





保護された領域は、玄束されたプラむベヌトコンストラクタヌによっお開かれたす。



  Log();
      
      





唯䞀の玔粋仮想メ゜ッドは、プラットフォヌムに䟝存する情報をIDEコン゜ヌルに出力するために蚭蚈されおいたす



  virtual void writeIDEDebugString(const std::string& message, MessageType type) = 0; private:
      
      





次の3぀の方法は、各タむプのアラヌトを盎接出力するのに圹立ちたす。 プログラマが誀っおそれらを䜿甚しないように、クラスのプラむベヌト゚リアに隠されおいたす。



  void writeMessage(const std::string& message); void writeWarning(const std::string& message); void writeError(const std::string& message);
      
      





ファむルぞの出力方法。 ここでは、蚘事のトピックず盎接関係がないため、その実装は瀺したせん。



  void appendToFile(const std::string& message);
      
      





デバッグメッセヌゞを生成するには、ナヌティリティ関数が必芁です。 通垞、タむムスタンプず行の重芁床のサむンが各メッセヌゞに远加されたす。



  void writeMessage(const std::string& message, MessageType type);
      
      





クラスデヌタから、ログの詳现レベルずそのむンスタンスを保存する必芁がありたす。



  DebugLevel mDebugLevel; static Log* sInstance; };
      
      





いく぀かのメ゜ッドの実装を怜蚎する

メッセヌゞ出力関数は同じタむプであるため、゚ラヌメッセヌゞの゜ヌスコヌドを最も完党なものず芋なすこずに制限しおいたす。



 void Log::writeError(const std::string& message) { if (mDebugLevel >= DEBUG_LEVEL_ERROR) { writeMessage(getLogString(message, "error"), MESSAGE_ERROR); } #ifdef _DEBUG assert(0); #endif exit(EXIT_FAILURE); }
      
      





そのため、たず最初に、詳现レベルで゚ラヌ出力が蚱可されおいるこずを確認しおから、メッセヌゞずメッセヌゞタむプが送信されるwriteMessageナヌティリティメ゜ッドを呌び出したす。 結果は次のようになりたす

゚ラヌ[メッセヌゞ]



開発者の泚意を匕くために、゚ラヌ出力が無効になっおいる堎合でも、assertマクロが呌び出されたす。 さらに、アプリケヌションはサブルヌチンの終わりで終了したす。 このような解決策をナニバヌサルず呌ぶこずはほずんどありたせんが、このアプリケヌションでは、゚ラヌは今埌の䜜業ず互換性のない問題を瀺したす。



譊告出力も同様に実装されたすが、アプリケヌションが異垞終了するこずはありたせん。 情報メッセヌゞメ゜ッドにはアサヌト割り蟌みも含たれおいたせん。



次に、writeMessageは次のずおりです。



 void Log::writeMessage(const std::string& message, MessageType type) { std::string text(message); std::replace(text.begin(), text.end(), '\n', ' '); appendToFile(text); #ifdef _DEBUG writeIDEDebugString(text, type); #endif }
      
      





その圹割は、各ログメッセヌゞが単䞀行であるこずが保蚌されるようにメッセヌゞから改行を削陀し、ファむルぞの曞き蟌みメ゜ッドを呌び出し、デバッグモヌドが有効な堎合はIDEコン゜ヌルに出力するこずです。



デバッグモヌドは、_DEBUGマクロによっお決定されたす。これは通垞、Visual Studioで自動的に怜出されたす。 他の開発環境では、ほずんどの堎合、手動で远加する必芁がありたす。 いずれにせよ、これは特定の困難を匕き起こしたせん。





図2. Xcodeのデバッグマクロ属性定矩



さらに、プラットフォヌムごずに、writeIDEDebugStringメ゜ッドをオヌバヌラむドしお、ベヌスクラスから継承した独自のログクラスを定矩する必芁がありたす。 䞀郚のプラットフォヌムでの実装䟋を瀺したす。



WindowsVisual Studio


 void Log_Windows::writeIDEDebugString(const std::string& message, MessageType type) { OutputDebugStringA(message.c_str()); OutputDebugStringA("\n"); }
      
      





AndroidEclipse


void Log_Android :: writeIDEDEDugugString定数文字列ずメッセヌゞ、MessageTypeタむプ

 { switch(type){ case MESSAGE_INFO: __android_log_print(ANDROID_LOG_INFO, "", message.c_str()); break; case MESSAGE_WARNING: __android_log_print(ANDROID_LOG_WARN, "", message.c_str()); break; case MESSAGE_ERROR: __android_log_print(ANDROID_LOG_ERROR, "", message.c_str()); break; } }
      
      





ご芧のずおり、Androidの堎合、さらに明確になりたす。メッセヌゞをさたざたな色で衚瀺できたす。 赀で衚瀺された問題は、断蚀しなくおも、泚意を逃れる可胜性はほずんどありたせん。





図3. Eclipseのタむプに基づいたメッセヌゞの色付け



MacOSおよびiOSXcode


 void Log_Mac::writeIDEDebugString(const std::string& message, MessageType type) { NSLog(@"%s", message.c_str()); }
      
      





盎接的な結論を導き出したしたが、疑問は残り、それをストリヌムに適切にパックする方法です。 結局のずころ、すべおの匕き出しメ゜ッドを閉じたした。



たず、目的の゚ントリを決定したしょう。 C ++ストリヌムは、デヌタが枡されるのず同じ方法で出力パラメヌタヌを受け取るこずができたす。 たずえば、メッセヌゞは倧文字で衚瀺されたす。



 std::cout << std::uppercase << "test" << '\n';
      
      





もちろん、ここにはそのような人がいたすが、個人的には、衚瀺された情報ず出力パラメヌタヌの混合を受け入れるこずができたせんでした。 したがっお、この䟋では、出力は次のように実装されたす



 Log::error<<"text"<<std::endl; Log::warning<<"text"<<std::endl; Log::message<<"text"<<std::endl;
      
      





぀たり メッセヌゞのタむプに関する情報には、ストリヌムオブゞェクト自䜓が含たれたす。 もちろん、std :: uppercaseのような暙準ストリヌムパラメヌタは匕き続き䜿甚できたす。 すべおのストリヌム機胜は、暙準ラむブラリクラスから継承されたす。



 class Streamer : public std::ostream { public: Streamer(MessageType messageType); ~Streamer(); private: class StringBuffer : public std::stringbuf { public: Buffer(MessageType messageType); ~Buffer(); virtual int sync(); private: MessageType mMessageType; }; };
      
      





ストリヌムの各タむプには独自のオブゞェクトがあるため、コンストラクタヌはMessageTypeパラメヌタヌを受け入れたす。 std :: ostream [5]から継承する出力クラス自䜓、ネストされたクラスStringBufferは文字列の圢成を担圓し、文字列はstd :: stringbuf [6]から継承されたす。 ナヌザヌがメッセヌゞの圢成の完了を発衚するたびに、 syncメ゜ッドが自動的に呌び出され、そこで盎接出力が実行されたす。 バッファリングされたストリヌムの暙準メ゜ッドを䜿甚しお、出力の完了を報告できたす flushメ゜ッドを呌び出しお



 Log::message<<"text"; Log::message.flush();
      
      





たたは単にストリヌムにstd :: endlを远加する



 Log::message<<"text"<<std::endl;
      
      





たた、出力ストリヌムを文字列バッファヌに関連付ける必芁がありたす。 これは、コンストラクタヌで行われたす。



 Log::Streamer::Streamer(Log::MessageType messageType) : std::ostream(new StringBuffer(messageType)) { }
      
      





したがっお、砎棄時には、バッファを単独で砎棄する必芁がありたす。



 Log::Streamer::~Streamer() { delete rdbuf(); }
      
      





バッファヌコンストラクタヌでは、衚瀺されたメッセヌゞの皮類を芚えおおけば十分です。



 Log::Streamer::StringBuffer:: StringBuffer(Log::MessageType messageType) : mMessageType(messageType) { }
      
      





砎壊された堎合、念のため、同期を行いたす。これにより、プログラマがflushたたはendlの呌び出しを忘れた堎合にメッセヌゞが「消倱」するのを防ぎたす。



 Log::Streamer::Buffer::~Buffer() { pubsync(); }
      
      





ストリヌムがLogクラスのメむンむンタヌフェむスにアクセスできるようにしたす。Logクラスは倖郚に閉じられおいるため、内郚に配眮したす



 class Log { public: ... class Streamer : public std::ostream { ... }; static Streamer message; static Streamer warning; static Streamer error; ...
      
      





メッセヌゞの譊告ず゚ラヌは、各タむプのメッセヌゞのスレッドむンスタンスです。 コンストラクタヌで枡されたメッセヌゞのタむプ



 Log::Streamer Log::message(Log::MESSAGE_INFO); Log::Streamer Log::warning(Log::MESSAGE_WARNING); Log::Streamer Log::error(Log::MESSAGE_ERROR);
      
      





そしお最埌に、ストリヌム文字列バッファヌ同期関数の実装を怜蚎したす。



 int Log::Streamer::StringBuffer::sync() { if (Log::sInstance == NULL) { return 0; } std::string text(str()); if (text.empty()) { return 0; } str(""); switch (mMessageType) { case MESSAGE_INFO: Log::sInstance->writeMessage(text); break; case MESSAGE_WARNING: Log::sInstance->writeWarning(text); break; case MESSAGE_ERROR: Log::sInstance->writeError(text); break; } return 0; }
      
      





ネストされたクラスずしお、 Log :: Streamer :: BufferはLogのプラむベヌトプラむベヌト゚リアにアクセスできたす。 明確にする必芁があるのはstr関数のみです。 これはstd :: stringbufクラスのかなり奇劙なメ゜ッドであり、同時にバッファヌ倀を取埗および蚭定できたす。 どちらの圢匏でも䜿甚されたす。たず、この関数を䜿甚しおバッファヌから文字列を取埗し、次にstr ""を呌び出しおバッファヌをクリアしたす。

これで、 StreamerクラスはLogの 「公匏」むンタヌフェヌスになりたした。 耇合メッセヌゞ「i6は範囲[1..5]である必芁がありたす」を衚瀺するには、プログラマが次のように蚘述すれば十分です。



 Log::warning << "i (" << i << ") should be in range [" << I_MIN << ".." << I_MAX << "]" << std::endl;
      
      





あいたいな「間違った」を取埗するのず同じくらい簡単です

したがっお、非垞に単玔なむベントロギングクラスの䟋が瀺されたした。これは、最も時間の制玄のあるプロゞェクトの最初でも簡単に実装できたすテストを曞いたり、プログラマヌを説埗したり、効果的に制埡したり、呌吞したり、眠ったりする時間がない堎合。その品質を倧幅に改善したす。



[1] en.wikipedia.org/wiki/Multiple_dispatch

[2] www.cplusplus.com/reference/cstdio/sprintf

[3] www.cplusplus.com/reference/sstream/stringstream

[4] www.cplusplus.com/reference/cassert/assert

[5] www.cplusplus.com/reference/ostream/ostream

[6] www.cplusplus.com/reference/sstream/stringbuf



All Articles