printfとtypedefの微妙な関係 - 無視されがち?なformt warning

型ができていない者が芝居をすると型なしになる。メチャクチャだ。
型がしっかりした奴がオリジナリティを押し出せば型破りになれる。どうだ、わかるか?
立川談志 - 『赤めだか』より

ひさびさにコーディングの話を書きたいと思います。組み込みの現場なんかで結構よく見かける話かと思いますが、以下のようなコードに対してでるwarningのお話です。

printfとtypdefの微妙な関係

ケースとしては、int等の組み込み型が環境依存なのを嫌って、typedefでint32_tやINT32という型におきかえることがあります。例えば以下のコード。
typedef long INT32;
int
main(int argc, char* argv[])
{
  INT32 val = 1;
  printf("val=%d\n", val);
  return 0;
}
さて、これをコンパイルすると、例えばgccなら以下のようなwarningがでます。(-Wallや-Wformatがある場合)
$ gcc -o test.c
test.c:9:22: warning: format specifies type 'int' but the argument has type
      'INT32' (aka 'long') [-Wformat]
  printf("val=%d\n", val);
-Werrorがついていればエラーになりますが、そうでない場合は、 実害なし、というか、別にあってるよね、くらいで無視されがちな気がしています。

実際のところ一般的な32bit処理系であれば、intとlongはどちらも符号付きの32bit整数型なので、 数値計算等で使っている範囲では全く同じなのですが、それでもwarningは出ます。なぜでしょうか?

INT32のようなtypedefを定義する理由は処理系依存の型を直接使わずに、 移植性を高めるために使われるものです。
(上の例なら、longが64bitになる64bitの処理系では、intへのtypedefに変えるなどする) 一方で、printfの%dは、そもそも処理系依存の型であるintを期待するものなので、 そこはintでなければ、処理系によっては不一致を起こすので、32bit系であろうと%dにはlongを渡す必要があるのです。

では、どうですればいいのでしょう?
上の例であれば、%dの代わりに%ld(signed long指定)を用いればwarningはなくなります。あるいは、引数側でintへのcastをしてやれば同じく問題はなくなります。
typedef long INT32;
int
main(int argc, char* argv[])
{
  INT32 val = 1;
  printf("val=%d\n", (int)val);
  return 0;
}
個人的には、printfのようなログ出力は処理系依存なので、引数の型の方にcastする上記やり方がスマートかと思います。 もちろんログなどではなくてファイルフォーマットにかかわるもので、移植時の型サイズ維持が重要なものはformat string側をかえるのが筋がいいものもあるとは思います。

「移植性をあげる」という目的で、処理系依存型を非依存型に変換するのはよく見かけるコードですが、一方で、非依存型から処理系依存型への変換というのは結構ないがしろにされがちです。strcmp等のstring系の関数でchar型を期待している関数を使う時も同じようなことがままあります。

なお、size_t, ssize_tについては、C99から専用のformat指定子zが定義されていますのでそれを使いましょう。(size_tから%zu、ssize_tなら%zd)

無視して良いwarning?

unsed-variablesとformatのwarningは無視されがちなwarningの2大巨頭ではないかと思います。どちらも大抵の場合は、確かに実害はないのですが、ごくまれにバグのものもあります。例えば以下のコード。
const char* name="taro";
printf("%d: name=%s\n", name, strlen(name)); 
引数順間違って%sに数値渡しているのでbuffer overrunをひきおこします。
ここで言いたいのは、「実害なし」という名目でwarningを残したり、 あえて、-Wno-formatのようにwarning抑制したりすることは、こういう本当にバグを埋もれさせかねないので、 基本的にはwarningは全て除去するというのは基本ポリシーとして実施すべきかと思います。

なお、デバッグ用にprintfを自作のマクロや関数でラップすることがありますが、 何もしないとprintfのformatチェックが機能しないことになり、 warning無視と同じくバグに気づくチャンスを逃すことになりかねません。 対策としては、例えばgccでは、独自定義マクロでもformatチェックするようにattributeしていることで コンパイラに指定する機構がありますので、あわせて活用することをおすすめします。
以下はgccの例です。
void debug_printf(const char* format, ...) __attribute__ ((format(printf, 1, 2)\
));

void debug_printf(const char* format, ...)
{
  va_list arg;
  va_start(arg, format);
  vprintf(format, arg);
  vfprintf(stderr, format, arg);
  va_end(arg);
}
上の例だと、__attribute__ formatという属性のを関数に付与することでformatチェックが入るようになります。引数の意味は、関数タイプ(この例だとprintf)、format文字列の位置(この例では1)、最初の引数の位置(この例では2)になっています。詳細は、以下gccのmanualが詳しいです。 ようするに、warningは正しく活用しましょうということですね。

なお、デバッグ出力用の可変個引数マクロやwarningの話は随分前にもとりあげていたので、よろしければご参考までに。

【関連記事】
可変個引数マクロを使う
warningに気を配る
使用しない仮引数

DVDで保存していた撮影動画をGoogleフォトで管理

忘れるにまかせるということが、結局最も美しく思い出すということなんだ。
川端康成 「散りぬるを」

前回に続いてmac環境でのGoogleフォトへの動画バックアップのTIPSを自分用メモもかねて紹介したいと思います。前回はAVCHD形式で撮った動画の保存についてでしたが、今回はDVDに保存した動画についてです。

さて、一口に「DVDで保存した動画」といってもいろいろな種類があります。PCにmp4形式のファイルをDVDにファイル保存したものもあれば、DVDやBlu-rayのレコーダでダビングしたものもあります。後者の場合、DVD-VideoやDVD-VR(Video Recording)といった形式で記録されることがほとんどだと思いますが、この場合、個々の動画が個別のファイルにはなっていないのでちょっと工夫が入ります。ここでは、多くのDVD/Blu-rayレコーダで採用されているDVD-VR形式で保存されたものについて説明したいと思います。

流れとしては、だいたい以下の手順になります。
  1. タイトル毎のファイルへの分割
  2. 撮影時刻の設定
  3. 拡張子変更
では順を追って説明します。

タイトル毎のファイルへの分割

Googleフォトはファイル単位で動画を管理するので、まずは撮影動画をファイル化する必要があります。このために以下のページで紹介されているdvd-vrというツールを利用します。 一応ツールのURLも。現時点ではv0.9.7でした。 このツールはコマンドラインベースのツールなのですが、実行バイナリではなくソースコードのみ公開されていますので、まずはビルドして実行ファイルを作成する必要があります。configureも必要ないので、適当なディレクトリで展開してmakeするだけでOKです。
$ tar zxvf dvd-vr-0.9.7.tar.gz 
$ cd dvd-vr-0.9.7
$ make
これで、dvd-vrという実行ファイルが作成されます。パスを通したい場合は、適切なフォルダに移動しましょう。

具体的なツールの使い方は上のURL参照頂ければいいかと思いますので、ここではポイントだけ説明します。基本的には、VR_MANGR.IFOファイルと、VR_MOVIE.VROファイルを指定してファイルを分割するだけです。
例として、タイトル確認 (パスが通っているところにdvd-vrがある前提で、DVD_VIDEO_RECORDERというボリューム名でマウントされているDVD内のタイトルを表示したいとした場合、コマンドとしては以下のようになります。
$ dvd-vr-0.9.7 /Volumes/DVD_VIDEO_RECORDER/DVD_RTAV/VR_MANGR.IFO
これでタイトルが確認できますので、次、ストリームファイルと分割後ファイルのprefixを指定してファイルを分割します。実際には既存ファイルを分割するのではなく、ファイルを区切りながらコピーするという動作になります。なお。分割後のファイルはコマンド実行したディレクトリ以下に作成されますので、あらかじめ分割後ファイルを入れたいフォルダに移動しておきましょう。
$ dvd-vr-0.9.7 /Volumes/DVD_VIDEO_RECORDER/DVD_RTAV/VR_MANGR.IFO /Volumes/DVD_VIDEO_RECORDER/DVD_RTAV/VR_MOVIE.VRO name=DVD
これで、DVD#001.vob, DVD#002.vob・・・というファイルが作成されます。

これで終了!と言いたいところですが、もう少し。
このままだと撮影日が本日になってしまいますので、これらに撮影日時情報を作成日及び変更日として記録します。
$ SetFile -d '09/24/2006 10:00:00' DVD#001.vob 
$ SetFile -m '09/24/2006 10:00:00' DVD#001.vob 
これでここのファイルへの分割というのは一端完了です。

拡張子の変更

上でできたvobファイルですが、残念ながらこのままだとGoogleフォトは****.vobをファイルとして見てくれません。vobの中身は基本的にはMPEGと同じでようなので、まとめてファイルをrenameします。
変更自体は以下のようなワンライナーで十分です。
for i in `ls *.vob`; do mv $i ${i%.vob}.mpg; done
ここで使っている部分一致の書き方は知らない方もいると思うので参考リンクあげておきますね。
これでDVD上の動画が全て.mpgファイルになったので、これらをアップロード対象ディレクトリに移動するなりすれば完了です。

DVDは保存やTVで見るには便利ですが、Googleフォトにいれると昔の動画もスマホで見れて楽しいですよ ^^)

B015XZLMKIパナソニック 1TB 2チューナー ブルーレイレコーダー 4Kアップコンバート対応 DIGA DMR-BRW1010
パナソニック 2015-10-16

googleフォトアップロード用に動画ファイルの変更日を作成日にあわせる

紙に書かず、心に書きとどむべし。
アンティステネス 「断片」

最近写真や動画の管理とバックアップはGoogleフォトに頼っているのですが、なかなか面倒なのがムービーで録画したAVCHD形式の動画。
当初、AVCHDをフォルダ毎HDDにコピーした先をGoogleフォトの同期先に指定して、アップロードするようにしていたのですが、MTSのままだとPCだとやっぱり使いづらい。
やっぱりmp4やmovに変換した方がいいのかなと思って、以下記事にあたりました。iMovieでかんたんにmov変換できるのですね。全然知りませんでした。
試しにSDカード上のAVCHDを取り込んでみると、たしかに変換される!名前がClip #1.movとかになるのはイマイチですが問題はないです。変更日は、取り込んだ日になっていますが、作成日もちゃんと引き継がれている。これはいい!

と、いうことで、早速いくつかたまっていたAVCHDをmovに変換しつつ取り込んだ後、Googleフォトのアップロードフォルダにコピー。これで完璧!!
と思ったのですが・・・

「日付が今日になってる。。。」

どうも、Googleフォトは作成日ではなく変更日をベースに日付を決定するようです。。そこは作成日じゃないのか、と言いたいところですが、そうはいってもむなしいだけなので対策を調べます。
結論としては、特に設定等はなさそうなので、Googleフォト上でアップロード後に手で日付修正するか、アップロード前に変更日を修正するしかなさそうです。

さすがに手で一つづつ作成日をコピーするのもバカバカしいので、shell scriptを用意しました。
#!/bin/sh

for i in `seq 1 $#`
do
	CREATED=`GetFileInfo -d "$1"`
	echo $1 ":" $CREATED
	SetFile -m "$CREATED" "$1"
	shift
done
これをchdate.shとかいう名前を実行パスの通ったところにでも保存して、変換後の動画の入ったディレクトリ上で、
$ chdate.sh *.mov
とかすれば完了です。
なお、これはmov変換時に変更日がかわったことによる対策ですが、MTSのままバックアップ取る場合は、mvコマンドやcp -pとかで日付が保存されるような形でバックアップとればもとの日付が保存されます。但し、SD上のデータが既に内部HDDからのコピーだったりすると、既に日付がコピー時のものなので、やっぱり今回のような対策は必要です。
そもそも、作成日を見てくれていれば話は早いのですが、まぁ無料でこれだけのサービスがある事自体がすごいわけで、わがままは言えないですね :-)

【関連書籍】
「シェル芸」に効く!AWK処方箋 斉藤 博文
[改訂第3版]シェルスクリプト基本リファレンス ──#!/bin/shで、ここまでできる (WEB+DB PRESS plus) 山森 丈範

Googleトレンドで見るバスワードの盛衰 - ユビキタスからIoTへ

怪しげな新興宗教の大半は、「目を覚ませ」と怒鳴りながら、
信者たちの目を瞑ったままにさせようとする。
ラッシュライフ』 - 伊坂 幸太郎

ここしばらく、といっても、かれこれ1,2年くらい頻繁に目にするようになったバズワード「IoT」, Internet of things, 日本語でいうところの「モノのインターネット」。今まではネットワークに接続されていなかった「モノ」が、ネットワーク、インターネットにつながり情報をやりとりすることで、新たな価値を生み出そうというもので、その「モノ」だけでなく、それらを利用したサービスやら、それを実現するための要素部品まで、あちこちで見かけます。なかには、もともとネットワークにつながっていたようなデバイスもIoTの仲間に入れられたりして、もうネットワーク機能がついたものなら何でもIoTという感じです。

思えば、一昔前に、クラウドというキーワードがもてはやされた頃、ネットワークを介してつながるサービスはなんでもかんでも「クラウド」とつけられて、「クラウドさえあれば何でもできる」みたいな風潮があったころを思い出します。 もちろん、IoTにせよ、クラウドにせよ、大きなイノベーションだと思いますが、本来の意味以上の拡大解釈とともに乱用されるさまは、まさにバズワードという感じです。

さて、Googleが提供しているサービスにGoogle Trends (グーグルトレンド)というのがあるのをご存知でしょうか。マーケティング等にかかわっている方は、よく使われているかもしれません。ある単語がGoogleでどれだけ検索されているかというトレンドをグラフで見ることができるツールで、話題のキーワードやその推移をみることができます。IoTっていつ頃から使われ始めたんだろう?というのを調べようと使ってみたのですが、いろいろ他のバズワードやキーワードも見てみると。結構面白かったのでいくつか紹介したいと思います。

ユビキタスからクラウド、IoTへ

カタカナ語と英単語がまざっているので、この結果は地域=日本に限定しています。

ubiquitous-web2_0-cloud-sns-iot.png

一昔前に一斉を風靡したバズワードといえば「ユビキタス」。私が社会人になった当時、これからはユビキタス社会だ!と言われていたのを思い出します。最近ではめっきり使われなくなったように思いますが、検索結果にもそれがあらわれていますね。
「IoT」については2014年あたりから伸びてきているのがわかります。「SNS」については、意外と結構前から検索件数としては多いのがわかります。カテゴリ限定していないのでノイズもあるとは思いますが。
なお、少し前から「スマートxxx」という単語もよく使われるようになっていたので、「スマート」もまぜてみようかと思ったのですが、単語が一般的すぎていまいちでした。そのかわりに「Web2.0」というこれまた一斉を風靡したキーワードをまぜてみました。2006-2007年をピークに減ってきて、最近ではめっきり使われなくなりましたね。

モバイルOS

スマートフォン向けOSの検索結果。今回は地域限定なしの結果です。
smarphone-os-share.png

これはあくまでGoogleでの検索数ですので、これがそのままマーケットシェアとイコールではありません。iOSについては、iPhoneやiPadという形で検索されることも多いのでその分少ないところはあるかもしれませんね。なお、マーケットシェアという点では以下の記事にグラフがありましたが、そんなにはずれてない気はします。 一時は企業用スマートフォンといえば、blackberryでしたが、最近ではAndroid, iOSにおされてすっかり低シェアになりました。world wideでみると今や1%以下のシェアのようですが、アフリカやアジアの一部ではまだ高シェアを保っているようです。 ナイジェリアでは40%もあるんですね。強いブランド力によってステータスシンボルであること、Blackberry Messenger(BBM)がキラーとなっていること、端末の更新頻度が低いことが寄与しているらしいです。自分たちの常識と、グローバル市場を十把一絡げの統計だけみていると、見えてこない事実があるという好例ですね。

Webブラウザ

比較といえば、やっぱりこれ。Webブラウザの比較です。
web browser google trends

これまた、検索数なのでシェアではありませんが、概ねシェアと連動してそうですね。実際のシェアはwikipediaにデータがありました。 これを見て驚いたのはアフリカの国々ではOperaがブラウザシェアNo.1だということ。貧弱なネットワークでは、Opera miniの圧縮転送がかなり重宝されているのがその要因だそうです。なるほど!

opera-stats.png

検索結果だけで全てが見えるわけではないですが、Googleトレンドはいろいろ比較するにはいいツールです。あれ?ということろは、違う角度でしらべると新たな発見もありましたし。今までほとんど使ってなかったツールですが、これからちょくちょく使っていきたいと思います。
B00SMGZ1UI
【関連記事】
【関連書籍】

モジュール分割とタスク分割 - 誰のためのデザイン?

困難は分割せよ
方法序説』 - デカルト

ソフトウェアの設計上重要な二つの要素、モジュール分割とタスク分割。その目的と必要性、効果とともに、初心者が陥りやすい過度な分割による弊害について書いてみたいと思います。

モジュール分割は誰のため?

まず「モジュール分割」ですが、その目的は、ソフトウェアの意味的、機能的な単位に分割することにより、可読性や再利用性、あるいは、複数人開発での分担効率をあげようというもの。ソフトウェア設計における最も基本的な部分であり、最終的な、成果物の品質を左右する重要な設計要素ですね。

さて、モジュール分割でググると、STS分割技法、TR分割技法などの技法を紹介するページがあたります。
例えば以下あたりはまとまっていて参考になります。 私の場合、基本組み込み開発の世界にいますが、開発の現場でこれら単語を聞くことは少ない気がします。組み込みソフトの開発が大規模化してきたあたりから、組み込みでもオブジェクト指向設計が浸透しているので、最近の「オブジェクト指向ネイティブ」な世代には、クラス設計とか、デザインパターンから入った人が多いからかもしれません。そういう方には以下記事が、構造化プログラミングを経てオブジェクト指向に至る歴史がよくわかっておすすめです。

さて、前振りはここまでにして、今回の主題に入りたいと思います。それは、モジュール分割はそのソフトウェアを搭載した製品やサービスのためのものではないという点です。モジュール分割の意義は、
  • 開発者のため : ソフトウェアの可読性、分業効率の向上
  • 部品の再利用のため : 成果物の一部(又は全て)を使った派生開発
にあります。

そうはいっても、実際は、ソフトウェアの品質は、それを作る「人」に依存しますので、開発効率をあげることが、プログラムの方が品質に直結するので、上の話は極論ですが、何のためかという観点は非常に重要です。この考えに立てば、「成果物として何をつくるか」よりも、「誰が作るか、どうやって作るか」、そして、「成果物の再利用のスコープはどこまでか」が、モジュール設計を考える上で重要になるからです。

なぜこんなことを強調するのかというと、ソフトウェアのモジュール設計を見ていると、過度なモジュール分割がなされているケースが多いように思うからです。 4788514346 もちろん、ソフトウェアの部品化や階層化は、再利用性という観点で非常に重要な観点なのですが、全てのプログラムがそれらを考慮しないといけないというわけではありません。むしろ、再利用性を全く必要としない部分について、過度なモジュール分割を行って、厳密なインターフェースを定義していくことで、開発者が増えたり、必要以上のドキュメントやテストが増えることになりかねません。内部設計としてきちんとモジュール化するのはもちろん必要ですが、そこを外部公開してしまったとたん、害の方が多くなることがあるので注意が必要です。また、拡張性が全く必要ないとわかっているようなケースで、過剰なフレームワークを作ったりするのも、同じく害しかありません。

本来のスコープを超えた過剰設計は、百害あって一利なしです。

タスク分割は何のため?

もう一つのテーマであるタスク分割について。モジュール分割が静的な分割であるのに対し、こちらはプログラムの動的なふるまいを決定する上で重要な要素です。タスク分割というと、uITRONや VxWorksのようなRTOSの設計がイメージされますが、Linux等の非リアルタイムシステムにおけるプロセル分割、スレッド分割でも同じような考え方は必要です。(※ 厳密にはメモリ空間の扱いとか、リアルタイム性の保証の話とかで、違う点はありますが。)
タスク分割の動機は大きく分けると以下二点になります。
  • (1) モジュール化のための分割
  • (2) 並列性、リアルタイム処理のための分割

(1)の方は、先のモジュール分割の延長ですね。その機能毎にタスクを割り当てようというものです。タスク分割にとっては(2)の方が本質で、基本的には独立した実行コンテキストをもって、並列処理させることが主な目的です。(2)をもう少し細かくわけると、
  • 処理要求の間隔とそれに対する処理時間の吸収のため、処理タスクをわける
    (例) 入力デバイスからのイベント受付と、それをキューイングして順次処理するタスクをわける
  • 互いに独立した処理の並列実行
    (例) 複数クライアント毎に処理タスクをわける
  • リアルタイム性の高い処理の優先実行
    (例) 緊急度の高いタスクに高優先度を割り当てて、優先的に割り込み処理させる。
あたりでしょうか。これらに該当しない処理は、実行時の要求事項という観点からは特にタスクにわける必要がないので、あるとすれば、(1)モジュール化のための分割、ということになります。

確かに、機能単位にタスクを分けるのは、ソフトウェア構造をわかりやすくする上で有用なことも多いのですが、これが過剰すぎる場合があります。分割された各タスクが各々完全独立、依存関係なく並列動作できるものなら問題ないですが、これが一連の手続きの前後、あるいは、上下関係出会ったりする場合が問題です。タスク分割するということは、タスク間のやりとりは、関数コールではなく、キューなりイベントフラグなりのメッセージになっているはずですが、この時のタスク間の機能呼び出しは、同期型のものもあれば、非同期のものもあるでしょう。ここでいう同期型は、要求メッセージを投げた後、結果応答があるまで、イベント待ちで要求元タスクを待ち状態にさせるというものを指しています。これって、単なる関数コールと何が違うのでしょうか?一件、独立性が確保されて、可読性があがったように思えるのかもしれませんが、 機能呼び出しがメッセージになると、
  • ソースコードツアーや問題発生時のバックトレースが困難になる
  • コールグラフが作れないので、静的解析が困難
  • コンテクストスイッチが入ることで、処理負荷が増える
ということで、実行効率や解析性を落とすことになります。

このようなモジュールに紐付いた不必要なタスク分割は、先に書いたような過度なモジュール分割がされたシステムでよく見かけます。おそらく、モジュール分割した単位で担当者がかわっており、それぞれがつくりやすいように個々でタスク設計をしたために、不必要なタスク間イベントリレーができてしまったのではないかと思います。タスク設計については、「分けることはいいことだ」では決してありません。システム全体としての最適化という観点を意識しての設計が必要です。

過ぎたるは猶及ばざるが如し

モジュール設計にせよ、タスク設計にせよ、ただ一つの正解というものはありません。どちらにも言えることは、「何をつくるか」というだけでなく、「誰がつくるか」、「拡張性のスコープはどこまでか」を考えないといけないということ、そして、それを踏まえて、過度な分割をしすぎていないか確認すること、が大切かと思っています。

何事も、「過ぎたるは猶及ばざるが如し」ですね。
4434159372
【関連記事】
【関連書籍】

Linuxでシリアル通信 - Ubuntu+minicom

眺めているうちに、いろいろ気づいてくる
You can observe a lot just by watching.
ジョセフ・マーフィー

組み込み系では、ターゲット端末にシリアル接続してコンソール経由で、ログとったり、デバッグしたり、あるいは設定したりということがよくあります。その時必要になるのが、シリアル接続用の端末プログラムです。WindowsだとTeraTerm、Linuxだとやっぱりminicomが定番でしょうか。Linuxでは、Qt4使ったcutecomも試してみましたが、個人的にはボタンがごちゃごちゃしてイマイチだったので、結局minicomに戻りました。

ということで、今回は、Linux(Ubuntu 14.04)のシリアル通信端末としてminicomを設定した時のメモです。以下記事を参考にさせて頂きました。
(参考)Ubuntuでminicom起動すると/dev/ttyUSB0のパーミッションがないと言われる

minicomのインストール

これはaptでinstallするだけ。
$ sudo apt-get install minicom

但し、残念ながら、これだけだと、デバイスファイルのパーミッション設定で弾かれて、エラーになります。
$ sudo minicom --device /dev/ttyUSB0
minicom: cannot open /dev/ttyUSB0: No such file or directory
No such file or directoryではなく、Permission deniedの場合もあるみたいです。

デバイスファイル(/dev/ttyUSB0)のパーミッション設定

ということで、ttyデバイスのパーミッション設定を行います。
最近は、RS232C端子がついているPCもなくなってきたので、USBシリアルが使うことがほとんどですね。なので、ここでも対応するデバイスファイルは/dev/ttyUSB0という前提で話を進めます。

まずは、/dev/ttyUSB0を使うためにユーザー(例.dms)をdialoutに追加します。
$ sudo adduser <ユーザー名> dialout

次に、/dev/ttyUSB0のパーミッション変更のためudevの設定を変更します。単純にchmodで直接パーミッション変更もできますが、USBシリアルの場合hotplugなので、抜き差しするたびに設定しないといけないことになります。
具体的には、ttyファイルの定義行に、MODE="0666"を追加しています。
$ sudo vi /lib/udev/rules.d/50-udev-default.rules
KERNEL=="tty[A-Z]*[0-9]|pppox[0-9]*|ircomm[0-9]*|noz[0-9]*|rfcomm[0-9]*",  GROUP="dialout", MODE="0666"

ちなみに、udevルールの書き方は以下あたりが参考になりそうです。
udev の設定をカスタマイズする - いますぐ実践! Linuxシステム管理

minicomの設定

これで、minicomで/dev/ttyUSB0にアクセスできるようになったはずなのでminicomを設定します。
minicom -sで設定起動しますので、Serial Deviceに/dev/ttyUSB0に変更します。 なお、default設定に保存する場合は、root権限がいるので、その場合はsudoつけて下さい。また、LANGが日本語だと表示が崩れるので、LANG=Cで起動した方がいいです。
$ sudo LANG=c minicom -s

ログの自動保存

シリアル通信を使う目的がログ取得なんかの場合、常にログはファイル保存しておきたいことがあると思います。minicomの起動オプションでログの保存ファイル名が指定できるので、常に日付ファイル名でログ保存するように設定しておくと、ログの取り忘れもなく便利です。
先のLANG=C設定とあわせて、以下のようなaliasを.bashrcあたりで設定しておくと便利です
(個人的に想い出深いscript)。
$ vi ~/.bashrc
alias minicom="mkdir -p ~/minilog ; LANG=C minicom -C ~/minilog/`date +%y%m%d_%H%M%S`.log"

これで、~/minilog以下に、ログが自動保存されるようになります。本当は、timestampもONにしたいのですが、これはコマンドラインからはできないみたいです。必要ならminicomのソースコード変更するしかないですね。
なお、ログ取得し忘れてもう一回、というのはよくある光景なので、minicomにかぎらず、WindowsでTeraTermという時にもぜひやっておいた方がよい設定です。(参考: Tera Termログ取得-自動でログの取得を開始する設定)。「ログが残っていれば。。。」ということはよくある光景ですので、開発プロジェクトでログ取得目的で使う場合は必須にしてもいい設定ですね。

487311585X
【関連記事】
【関連書籍】

バグは誰のものか - エラー処理の責務分担

まかせてはいるけれども、たえず頭の中で気になっている。そこでときに報告を求め、問題がある場合には、適切な助言や指示をしていく。それが経営者のあるべき姿だと思います。これは言いかえますと“まかせてまかせず”ということになると思います。まかせてまかせずというのは、文字どおり“まかせた”のであって、決して放り出したのではないということです。

前回単体テストに関する話を書いたのでその続きで。

単体テストという言葉の定義はプロジェクトによって様々ですが、もっとも多いのは”関数”を単位とするものかと思います。関数単位のテストでは正常系のテスト(関数が適切に使われた時のテスト)以外に、異常値を与えられた時のテストも行われます。例えば以下の例。
const char*
month_name(int month)
{
  const char* name[] = {"Jan", "Feb", "Mar", "Apl", "May", "Jun",
                        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

  if (month < 1 || month >= 12} 
    return NULL; /* invalid month */
  return name[month - 1];
}
月を文字列に変換する関数ですが、1〜12以外はエラーとしてNULL(0)を返しています。
このような関数の単体テストでは、1〜12の値以外に、エラー値として、0や-1, 13等も異常値としてテストすることになると思います。

このように、多くの関数では、引数として取りうる値や条件が限られている場合が多く、それを満たさない場合は、エラーを返すというのはよく見られることです。しかし、このようなエラー処理は、毎回必要なことでしょうか。

例えば、この関数が以下のような関数の内部で使われている場合を考えます。
int
print_date(int month, int day)
{
  if (month < 1 || month >= 12} 
    return -1;
  .....
  printf("%s-%d\n", month_name(month), day); 
}
先ほどと同じ範囲チェックがここでもされています。もし、month_name()がここでしか使われない、つまり、この関数の専用内部関数なのであれば、month_name()内の範囲チェックは冗長です。言い換えると、month_name()内のエラーチェックは内部のバグでもない限り発生することはありません。そもそも、print_date()では、month_name()でエラー、つまり、NULLがかえってくることを想定していないので、万が一NULLが返ってきたらNULL pointerアクセスでsegmentation fault等を引き起こします。

非常に単純な例ではありますが、このようなケースでは、month_name()でのエラー処理は不要で、必要なのはアサーションになります。つまり、以下のような形です。
const char*
month_name(int month)
{
  const char* name[] = {"Jan", "Feb", "Mar", "Apl", "May", "Jun",
                        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  assert(month > 0 && month < 12)
  return name[month - 1];
}
こう書くことで、この関数は前提条件として、引数に1から12しかとらないということが表現されます。範囲チェックをさぼっているのではなく「上位にまかせている」ということがこれではっきりします。アサーションであれば、処理されないようなエラーを返すコードもないので、そもそも単体テストも必要ありません。万が一、上位関数のロジックが間違っていれば、assertにひっかけて早期検出も可能です。
よく混同されがちですが、アサーションはエラー処理の一種ではないのです。

じゃあ、どうやって使い分けるの?という話になりますが、必ずしも一概には言いきれないものの、一般論として言えば、外部要因によって値が不正になるような場合(例えば、ライブラリの外部公開関数や、データが外部ファイルやネットワーク、ユーザー入力に由来するなど)には、エラー処理が必要になりますが、内部関数の場合は、大抵はエラー処理ではなく、アサーションが適当な場合が多くなります。

よく、エラー処理とアサーションが混同されているコードに以下のようなものがあります。
int
print_date(int month, int day)
{
  if (month < 1 || month >= 12) {
    assert(0);
    return -1;
  }
  .....
  printf("%s-%d\n", month_name(month), day); 
}
エラー処理の中に、assert(0);が書かれており、どっちつかずになってしまっています。但し、この関数が通常外部関数で使われているものであり、デバッグ用にエラー発生次第abortするという機能として、コンパイルオプション等で有効無効が設定できるようなアサーションなのであれば、それはデバッグを効率化するひとつの方法だと思います。

ここまでの例では、引数チェックの話を例にとりましたが、戻り値のチェックでも同じことが言えます。
  ....
  ercd = loc_mtx(MTX1); /* lock mutex */
  assert(ercd == E_OK);
  ....
mutexの取得失敗をassertで確認しています。通常、mutexの取得はパラメータエラーや、同一タスクでの二重取得等の内部ロジックの不具合でしか起こらないことが多く、ここでエラーを返しても上位関数ではどうすることもできないので、このような場合は、アサーションが適当です。(失敗するはずはないという表明になる)

もし、これを以下のように書こともできますが、結果としての処理は同じでも、コードとしては通りもしないエラー処理を書くことになり、C0やC1カバレッジを必要とするような場合には、困ることになります (通らないはずのコードなのだからパスを通しようがない)。
  ....
  ercd = loc_mtx(MTX1); /* lock mutex */
  if (ercd != E_OK) {
    printf("loc_mtx failed : %d\n", ercd);
    abort();
    ....

非常に単純な話をくどくどと書いてしまいましたが、各モジュールや関数の責務をはっきりさせ、効率的にテスト、デバッグする上で結構重要な観点だと思います。
B00JEYPPOE
【関連記事】
【関連書籍】

ソースコードレビューと単体テストの違い

我々の犯す一つの大きな間違いは、原因を結果の間近にあると考えることにある。
ゲーテ

ソフトウェア開発において、できるだけ上流で欠陥を検出し、不具合を下流に流出させないことは、成果物の品質確保と開発全体の工数削減のために非常に重要になります。そのための代表的な手段が、「ソースコードレビュー」と「単体テスト(ユニットテスト)」です。
この二つ、どちらも同じようなもので、どちらか一方で、もう片方を代替できると思われることもありますが、実際に大きな違いがあります。今回は、その違いについて書きたいと思います。

ソースコードレビューと単体テストの違い

ソフトウェア開発のモデルでV字モデルと言われるものがあります。要求分析から詳細設計・コーディングまでの上流工程に対して、それぞれの工程に対応するテスト工程によって、その成果物が検証されるようなモデルです。

V字モデル

単体テストは、いわゆる詳細設計に対応するテスト工程になっていますが、ソースコードレビューはどこにマッピングされるものでしょうか。コーディングの一環と見方もできますが、単体テストと同じく、実装完了したソースコードを対象とするものですので、単体テストと同じく、詳細設計に対応する検証工程と考えるのが普通かと思います。

では、単体テストとソースコードレビューはどちらか片方だけすればいいものでしょうか。以下で紹介されている内容がわかりやすかったのでご紹介したいと思います。 ここに書かれている内容の要点だけまとめると、以下のような形になるかと思います。
  • ソースコードレビューも単体テストもINPUTは同じ。どちらも関数レベルのソースコード。
  • 検出率としてはソースコードレビューの方が優秀。
  • ソースコードレビューは、レビュアーのスキルレベルに依存し、見落としもあり得る
  • ソースコードレビューが品質向上工程で、テストの方は品質保証工程
品質向上と品質保証という言い方は、わかりやすいですね。

結局、両方必要ということなのですが、これらの違いはもう少し理解して、使い分けないと効果的な検証にはなりません。この違いをもう少し考えてみます。

テストは結果、レビューはプロセスを検証する

単体テストは、入力に対し、期待する結果がでるかを確認します。単体テストの指標として命令網羅(C0)や分岐網羅(C1)といったカバレッジ指標を用いることがありますが、これはあくまでテストの網羅率を計測しているものであって、評価そのものではありません。あくまで、検証対象は、INPUTに対するOUTPUTのみです。つまり、単体テストで保証しているのは、テストした範囲での入出力が仕様を満たしているということだけであり、設計の中身については何も保証してくれません。

では、ソースコードレビューの方はどうでしょうか。こちらは、入出力の対応を見るというよりは、関数の実装内容、ソースコードの中身を確認するのが中心になります。つまり、単体テストとは逆で、中身(経過)が正しいことを確認して、そこから結果が正しくなるはずということを検証するアプローチです。言い換えると、結果ではなく中身を検証するアプローチです。もう少しいい方を変えると、設計内容を検証するというものです。

そう考えると、単体テストはホワイトボックステストに分類されるかもしれませんが、関数レベルではブラックボックステストです。結果しか見ていない以上、単体テストだけで品質を保証するには、関数内が少しでも変われば、基本的には全部をテストしないと正しいということは言えません。ですので、効率的な単体テストで重要なのは、その再現性、すなわち、テスト実施者への依存性がなく、コード修正のたびに検証を効率的に実施できる仕組みになります。

一方、ソースコードレビューの目的は、その中身、つまり設計を検証することにあります。ソースコードレビューが、単なる機能レビューみたいになっているケースがありますが、それだけではソースコードレビューを実施する目的からすると不十分です。 ソフトウェアの実装は、たとえ関数レベルであっても、その作りによって、実行効率や拡張性、可読性は大幅に変わってきます。こういった、単なる結果が正しいかというだけではない、設計観点での検証がソースコードレビューでは重要になってきます。
よって、より良いレビューのためには、レビュアーの選定が重要になります。システムやサービス、商品の仕様に精通しているというレビュアーだけではなく、ソフトウェア設計の観点で適切なレビューができるレビュアーを入れることが重要です。そうでないと、ソースコードレビューが、機能レビュー、仕様レビューになってしまいます。

静的解析で単体テストとソースコードレビューの穴を埋める

詳細設計、コーディングプロセスに対する検証方法として、単体テスト、ソースコードレビューと並んで有用な方法が、静的解析です。有名どころでは、PG-ReliefやCoverity Quality Adviser等のツールを活用した静的なソースコード検証です。大規模なソフト開発になると、網羅的なソースコードレビューは非常に困難になりますが、静的解析のような機械的なチェックはその補完として非常に強力なツールになります。なお、一口に静的解析パターンマッチ型やビルドキャプチャー型等の種類があり、それぞれ得意とするスコープが違いますので、目的とするプロジェクトにあったものを選ぶことが必要です。わかりやすい説明があったので、リンクを貼っておきます。
今回は、ソースコードレビューと単体テストの違いをテーマに少し書いてみました。ソフトウェア開発のプロセスでは、様々な手順やアウトプット等がルール化されていると思いますが、単にルールだからといって実施するのではなく、っそのメリットや目的を正しく理解することが、効率的な開発及び品質向上のためには重要かと思います。

4817192631 【関連記事】
【関連書籍】

本のおすすめ

4274065979

4844337858

482228493X

4904807057

4873114799


プロフィール

  • Author:proger
  • 組み込み関係で仕事してます

ブログ内検索