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 【関連記事】
【関連書籍】

[C言語] 配列の添字に負の値は使えるか

今は終わりではない。これは終わりの始まりですらない。しかしあるいは、始まりの終わりかもしれない。

C言語で配列の添字に負の値を使うのは有りでしょうか。つまり以下のようなコードです。
  a[-1] = 0;
C言語では、配列int a[n]は、*((int*)(((int*)a + n))と等しいということなので、aが配列名、つまり、
  int a[10]
のように定義されたものなら、それは範囲外アクセスになってしまいます。
しかし、
  int b[10]
  int* a = &b[1];
のようなものであれば、*(a-1) = b[0]なので、問題なく動作します。

ということで、一応、言語的には使えなくもない負の添字ですが、不用意に使わない方が良いです。
例えば、以下のように有効な配列の範囲外を指すような使い方は、たとえ値参照しなくても使うべきではありません。
  int b[10]
  int* c = &b[-1];
C言語的には、ポインター 演算は、一度に割り振られた領域と、仮想的な"終端"を越えた1つめ の要素にだけ定義されていて、それ以外では未定義になります。よって、aが有効なアドレス空間の先頭に位置していた場合などに、a[-1]が有効なアドレスになるとは限らないということがありえます。
また、負の添字以前に、配列の添字に符号付き(signed)の型を使うのは、気づきにくい不具合を生む可能性があるという話もあります。例えば、次のようなコード。
#define ARRAY_SIZE 200
int my_array[ARRAY_SIZE];

int array(int n)
{
    if (n >= ARRAY_SIZE) {
        return -1;
    }
    return my_array[n];
}

単純なエラーチェック付きアクセサですが、これまた単純な配慮漏れがあります。
そう、nが負(マイナス値)の時の配慮が入っていないのです。

そのため、nがマイナスで渡されると、エラーチェックをすり抜けて、範囲外アクセスが発生します。この例では参照のみですが、値更新するような関数なら、任意の場所が書き換えられてしまうようなバグです。

さらに、あぶないのは以下のようなコード。
#define ARRAY_SIZE 200
int my_array[ARRAY_SIZE];

int array(int n)
{
    if (n >= ARRAY_SIZE) {
        return -1;
    }
    return my_array[n];
}

int func()
{
    char n = 150;
    printf("%d\n", array(n));
}
今後は、nがchar型になっています。char型は処理系によって、符号付きの場合と、符号なしの場合がありますが、符号ありの場合、150という値は保持されず、負の値になってしまいます。よって、負数を意図して使おうとしているわけではないものの、結果的に負数を使ってしまっています。
gccでは配列添字にcharを使っていると警告されるオプション(-Wchar-subscripts)があります。
charの暗黙の型変換は、いろんなバグの温床になるので、そもそもサイズのような変数の型にcharを使うのは、配列の添字に限らずやめるべきです。この手の変数については、符号なし型、できればsize_t(C言語ならstdlib.hで定義)を使うのが適切です。最新のC言語仕様であるC11の処理系であれば、オブジェクトの最大値を表す型としてrsize_tも使えます。


以上、C言語における配列の添字に負の値、あるいは、符号型を使った時の動作とリスクのお話でした。
この手の話は制約を理解して使えばいいという見方もあるかもしれませんが、ソフトウェア開発の現場等では、あとで別の人がメンテナンスする時の罠になりかねません。実際の現場では、あまりトリッキーなコードを書くのは控えておくのが望ましいですね。

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

起こりそうにないバグが起こるのはなぜか

落としたトーストがバターを塗った面を下にして着地する確率は、カーペットの値段に比例する

発生確率の非常に低いバグ。窓は狭いし、条件も複雑。
そんなものがよりにもよって、こんなタイミングで、こんなところで発覚するなんて。。。
いわゆるマーフィーの法則? どう考えても運悪すぎない?

・・・というようなこと、経験ないでしょうか。

そうそう起こるとは思えないような発生確率が低い、発生条件も非常に限定的な不具合が、よりにもよって、最悪の時、場所で見つかる。しかも、しょっちゅう。。自分自身も「なんて引きが悪いんだろう。。」って考えていたこともありますが、よく考えれば答えは簡単です。

『発生確率が低い不具合が、たくさん残っているから』

100万分の1の確率で発生するバグだって、そんなバグが100万個残っていれば、すぐにどれかにあたります。『下手な鉄砲も数撃ちゃ当たる』。いや、『ゴキブリを一匹見かけたら◯◯匹いると思え』ですかね。母数がたくさんあれば、当たりもひいてしまいます。

なので、こういったバグを引いた時は、単に「運が悪い」といって片付けるのではなく、まだ見ぬ低確率のバグがまだまだ残っている、言い換えると十分バグは収束していないという認識をもって、次にどういうアクションをとるべきか考えることが重要です。とりあえずの対症療法で済ませるのか、根本対策すべきかなど、その対応は開発ステップによってかわってくるでしょうが、品質のレベルを正しく認識、共有することは、結構、意識的or無意識的に無視orごまかされがちなので、注意したいところです。

4873114799

【関連記事】
このバグ直しますか? - 原因療法と対症療法
バグを潜伏させない工夫
デバッグと不確定性原理
デバッグ指向のススメ


【関連書籍】

高速ファイル検索ソフトEverythingの導入と設定

おまえ達のやっていることは検索で、思索ではない
伊坂幸太郎 - 『魔王』より

Windowのファイル検索ソフトの定番『Everything』。Windows標準機能の検索等と違って、あらかじめ作成したインデックスを使っての検索なので非常に高速です。正規表現が使えたり検索ツールとしても優秀なのですが、インクリメンタルサーチでとにかく動作が非常に高速なのが非常に便利。在処がわかっているファイルでも、Everything経由で開いた方が早いので、ファイルを開いたりという作業はほとんどEverythingだけで事足ります。

インデックスの作成もNTFSのインデックス情報を使っていて非常に早いのですが、それでもファイルが増えてくると結構重たくなってきます。起動後一回だけの話なのですが、たとえ10秒程度でも待ちが入ると、思考も中断されてしまうので、いまいちです。対策として、Optionに「システム起動時にEverythingを起動する」という設定があるのですが、Windows7ではUAC(ユーザーアカウント制御)にひっかかって、期待道理に起動してくれません。

対策としては、タスクスケジューラ経由の起動に登録すればOK。タスクスケジューラは、スタートメニューの「アクセサリ」→「システムツール」→「タスクスケジューラ」で起動可能です。詳しい手順等は、以下が参考になります。

うまくいけば、起動時にEverythingがバックグラウンド起動してくれるので、ファイル検索したい時には、瞬間的に検索結果が得られます。Everythingの起動をメニューやランチャーからすると、「本当に実行しますか」というUACの警告がまた出るので、起動ショートカットキーを定義(Option)しておいて、それ経由で起動するようにしておくと完璧です。

まだ、使ってない方は、この機会にぜひどうぞ。

【関連記事】
WindowsでCUI環境

【関連書籍】
ライフハッカー式整理のアイデア122―賢い人はなぜいつも机がきれいなのか? 小山 龍介
デスクワークを3倍効率化するテクニック―エクセルの3つの機能で仕事のスピードを加速する 奥谷 隆一

本のおすすめ

4873115655

4274065979

4822236862

4274068579

4822255131

B00SIM19YS


プロフィール

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

ブログ内検索