cutlassfish について

Embedded をバックグラウンドに持つ似非組み込みエンジニア。 現在はとある Web 系企業のバックエンドエンジニアをやってます. 気づけばもうすぐ 34 に... アラフォー目前...

GNU Make を単体テストのテストランナーに使用しようとして困ったことあれこれ

make test で単体テストが実行されるようになっているプロジェクトを CI/CD に対応させる際に色々と右往左往してしまったので make マスターの面々からするとヤレヤレな内容かもしれませんが… メモがてら.

ビルドシステムに make が採用されている場合、多くの場合サブディレクトリ毎に makefile が用意されていて、それをプロジェクトのトップディレクトリに置かれた makefile から再帰 make するというのがよく行われると思います.  例えば以下のような構成のプロジェクトでは (a) から (b), (c) の makefile を再帰 make すると言った具合です.

├── Makefile          — (a)
├── subdir0
│      └── Makefile  — (b)
└── subdir1
│      └── Makefile  — (c)

また、(b) (c) の makefile には疑似 ターゲットとして test が定義されており、各々のディレクトリで make test を打つことで lib の生成や単体テストのビルドと実行が行われるようになっています.  このプロジェクトを CI/CD 対応しようとしたとき、ツール側から沢山あるサブディレクトリを一つ一つ個別に叩いて行くのは面倒なので当然 (a) に定義された test ターゲットを叩いて済ませたくなる訳ですが、単体テストの実行には一般的に以下の要求事項があり、この要求を満足する形でテストランナーを実装しなくてはなりません.

(1) 途中で fail しても後続のテストは実行したい.
(2) 全てのテストを実行し終えた後に一つでも fail していた場合に全体の終了ステータスをエラーにしたい.

さてどうしたものかと言うことで、真っ先に思いついたのが for でサブディレクトリを回す方法ですが、この方法では呼び出し元の make にエラーが帰らないため、(2) の条件を満たす事ができません.

次に思いついたのが、以下のように test ターゲットのコマンド行に再帰 make を直書きして -k (–keep-going)オプションで make を起動するという方法でしたが、-k オプションはターゲットのコマンド行で発生したエラーを無視して続行するのではなく、エラーを起こしたターゲットの構築を中断して、別のターゲットの構築を続けて実行するというオプションのようなので期待した動作をしません.  (というのをやってみて気づいた)

Normally make gives up immediately in this circumstance, returning a nonzero status. However, if the ‘-k’ or ‘–keep-going’ flag is specified, make continues to consider the other prerequisites of the pending targets, remaking them if necessary, before it gives up and returns nonzero status. For example, after an error in compiling one object file, ‘make -k’ will continue compiling other object files even though it already knows that linking them will be impossible.

https://www.gnu.org/software/make/manual/html_node/Errors.html

そうこうして最終的に「サブディレクトリ毎に test_subdir0, test_subdir1 のようなテスト専用ターゲットを (a) に用意して -k オプションを付けて叩くしかない」という結論に至ったのですが、実際はディレクトリの数も多く、ベタ書きはダサいなー嫌だなーと言うことで無い知恵絞ってヒネリだしたのが以下

なんかとりとめもなくなってしまった&もっといい方法がありそうな気がしてならなですが、とりあえず要件は満たせた!??

広告

「コードの綺麗さ」と言う言葉に対する表面的な理解

「コードの綺麗さ」といった場合、いくつか評価軸がありますが恐らくほぼ以下の 3 つに集約できるのではないかと思います.

(1) 関数内部、ファイル内部等の局所的に記述されているコードの見た目 (改行とかスペースとか) が綺麗かどうか
(2) 関数内部、ファイル内部等の局所的に記述されている処理や記述に無駄なく美しいかどうか
(3) モジュールの切り分け、命名等を含む、大域的に見た設計が統一され美しいかどうか

もちろん全部綺麗なことに越した事はないですが、(3)が最も重要度と抽象度が高く、逆に(1)は個人的にそこまで重要では無いと思っています.

何故なら、見た目の統一感の無さよりアーキテクチャ上の統一感の無さの方が余程問題だから. どうしても見た目を揃えたいならツールにやらせれば良いことで、このような作業を人間が時間を割いてやるほどアホで愚かな事はありません.  (後れ馳せながら最近仕事で golang を触り始めたのですが、go fmt を処理系と一緒に用意して宗教戦争も含めた無駄を排除するという考え方、すごく良いなと思いました.)

ですが、現実にはコードの綺麗さを(1)でしか測れず、コードレビューの際にもこういった見た目の指摘ばかり繰り返す人間がかなりの割合でいます.  そういう人達に欠如しているのは、本質が何なのかを理解する能力や、コードを俯瞰してみる(つまり木ではなく森を見る)と言う視点です.

優れたソフトウェアほど一貫した設計思想があり、その思想を理解することで細部を読まずとも全体を想像し、構造を把握する事が出来ます.

自分は何度か転職をしていますが、一番最初と現在の会社を除いてこのような視点を持っている人間は皆無でした.

以前自チームで構築したプログラムを同僚に解説せよと上から仰せつかった際、全体の規模が大きかった事もありアーキテクチャーの解説を中心に行った所、「お前の説明はなっていない」と言わんばかりの感じで何故それが知りたいのか理解出来ない細部の説明ばかりを求められた事があります.

転職の際の引き継ぎだったので無表情で言われるがまま応じましたが、言うまでもなく死ぬほどウンザリしました.

残念な組織ほどこういった本質に対する理解力の欠如した人間が大きな顔をし、挙句の果てにコーディング規約なんか作っちゃってるからもう…

C++ で Read-Write lock パターン

同じデーターに対して複数のスレッドが入り乱れて読み書きを行う場合、排他のオーバーヘッドが無視できずに問題になることがあります.

そこで Read アクセスのみの場合は排他を行わなわず、Read-Write がかち合った場合にのみ排他を行うことでオーバーヘッドの低減を図ろうというのがこのパターンです.

Read-Write lock パターンは shared mutex という形で標準ライブラリで提供されています (shared mutex の詳細については以下のリンクを参照下さい)

cppreference

C++ でマルチスレッドデザインパターン

C++ で Thread-Specific Storage パターン

複数のスレッドから読み書きされるデータはその内容の整合性を保つため、基本的には排他処理を行う必要があります(Single Thread Execution パターン

Thread-Specific Storage パターンは同じデータに複数スレッドからアクセスが行われ、かつスレッド間でのデータの共有が不要といったケースにおいて、スレッド毎に専用の領域を用意する事で排他を行わずに並列実行可能なようにすると言うパターンです.

C++11 から、新たな記憶クラス指定子として thread_local というものが用意されており、これを使うと簡単に Thread-Specific Storage パターンを使うことができます (thread_local 記憶クラス指定子についての詳細は以下のリンクを参照下さい)

cppreference

このパターンの実際の使い所ですが、積極的に使用するケースというのはあまりなく、「シングルスレッドでの使用を前提に静的領域を多用している(場合によってはレガシーな)コードを比較的簡単にマルチスレッド化する」といったような用途で使用される事が多いような気がします.

C++ でマルチスレッドデザインパターン

MacOS Sierra 10.12 + Xcode 8 + Qt 5.9.1 で QMake 時に「could not resolve SDK Path for ‘macosx’ 」 エラーになる問題

転職直後の大変さもだんだん落ち着き、念願叶って開発環境もWindows から MacOS & Linux になったので 「よし Mac に Qt の開発環境を準備しよう!」と思い立ってハマりまくったお話.

開発環境を作ると言っても、いつも通りインストーラー落としてきてインスコするだけだろうと思ったら QMake 時に「could not resolve SDK Path for ‘macosx’ 」と言って怒られてしまう…

最初にこんな物を見つけて、やってみるけどダメ…
https://stackoverflow.com/questions/26320677/error-could-not-resolve-sdk-path-for-macosx10-8/26321074

困り果てて、ダメ元でもう一度インストールしてみると Xcode 5.0.0 を入れろと警告してくる.  最初は「Xcode ? あぁー後で入れる入れる」と適当に流していたんですが、「Xcode 5.0.0 !? どんだけ古いねん! うぇーもしかして Qt の MacOS への対応ってすごい遅れてる???」とか思って、Apple の developper サイトから Xcode 5.0.0 を落として来てみるも、古すぎて Sierra にはそもそも入れられない…

転職でキャリアチェンジして仕事で Qt を弄らなくなってしまったというのもあり、その後も暫く空き時間やプライベートの時間に試してはダメを繰り返して、今日以下のポストを参考にやっと解決しました.

https://stackoverflow.com/questions/41513456/qt-5-7-xcode-8-1-os-x-el-captain-could-not-resolve-sdk-path-for-macosx

結果的に Xcode 上から一度どの Command line tool を使用するかの選択をしないと、Qt が参照している PATH 系の何かがセットされないような感じっぽい??

まさかこんなにどハマリするとは予想していませんでしたが、 とにかく情報が全然出てこないので Mac の Qt develper にはもしかして常識なのか、Xcode での開発をガン無視して初っ端から Qt で開発しようとしている自分がアレなのか…

ともかく毎度の事ながら stackoverflow さまさまでした… ありがたい

C++ で Thread-Per-Message パターン

Thread-Per-Mesasge パターンとは時間を要する処理要求に対し、一つ一つスレッドを割り当てる事で応答速度の向上を目的とするパターンです.

例えば以下は時間のかかるファイル保存の処理に Thread-Per-Message を適用した例です.

ここではダミーで標準出力へ出すようにしていますが、 Helper は処理の本体であるファイル保存を行うクラスで Host は client からの要求を受け、処理を行うスレッドを起動するクラスです.  スレッドはインスタンス化と同時に detach され、Host とは完全非同期に処理を実行します.(上記のような簡単な例だと Helper を利用するのが大げさに感じてしまいますが、本来のパターンに忠実に従い上では Helper を定義しています)

さてこの Thread-Per-Message パターンですが、実は使い所が大変難しいパターンだと思います.  なぜ難しいかと言うと スレッドは生成と同時に detach されて生成元のあずかり知らないところで動作を行うため、生成元で処理の終了やエラーの発生を感知できないからです.

では逆にどんな処理なら問題無いかと言うと

(1) 絶対エラーが発生しない (もしくは発生しても問題ないと) と分かっている単純な処理.
(2) deadlock 等が発生せず、一定の時間で終了することが保証されている処理.
(3) スレッドで実行される処理が非同期に動作するプログラムとして高い独立性を持っており、エラー処理等を完全に自己で完結出来る場合.

の場合のどれかに当てはまる場合でしょう.  (1) は言うまでも無いと思いますが、(2) の場合 deadlock を起こしたスレッドが開放されない事で徐々に OS のリソースを食いつぶして行き、最終的に処理速度の低下やスレッド生成の失敗等につながる恐れがあります.  (3) は Apache 等の Web サーバーがリクエストを捌く際に行う処理がまさにそれです (Apache の場合はリクエスト毎にスレッドでは無くプロセスを起動するので Process-Per-Message というのが正しいですが..)

というわけで今回取り上げたファイルに保存すると言う例も、ログファイルなどの「まぁ、問題が起こったら保存されなくてもしょうがないよね」というような用途以外で使用するのは適切ではありませんのでご注意を.

C++ でマルチスレッドデザインパターン

 

C++ で Balking パターン

Balking パターンとは「オブジェクトに対しての処理要求に対し、オブジェクトが特定の状態にある場合にのみ処理を行う(そうでない場合は何もしないで抜ける)」というパターンです。

例えば GUI システムでは多くの場合 main スレッドが UI のイベントループをハンドルしているため、lock 待ち等で main スレッドが待ち状態に入ると UI がフリーズしてしまいユーザビリティーの観点から好ましくありません。その場合 try lock のような仕組みを利用して確実に lock が取得できる場合のみ lock 取得処理を行う(そうでない場合はイベントループを回し、lock が取得できるようになるまで待つ)と言うことが良く行われます.

上記がここで取り上げた例の擬似コードになります.  パターンの適用例としてはあまり紹介されていないように思いますが、これも一種の Balking パターンだと思います.

C++ でマルチスレッドデザインパターン