ろぐれこーど

限界組み込みエンジニアの学習記録とちょっぴりポエム

copytreeのignoreをinclude/excludeで指定する(python)

pythonにてディレクトリを再帰的にコピーする場合、shutil.copytreeを使用できます。このとき引数ignoreを使用すると指定したglobパターンにマッチするファイル、ディレクトリを除いてコピーができますが、指定の仕方がちょっと特殊(shutil.ignore_patterns()にてコールバック関数を生成)だったので、

  • 無視したいglobパターンをリストで指定(ignore_patterns()は可変長引数を取るため、iterableで渡せない)
  • 除くファイルではなく含むファイルを指定

みたいなことができないようです。事務作業するときにちょっと困ったので、自分なりにignore_patterns()に代わる関数を書いてみました。

環境は以下。

結論

以下コード参照

copytree-ignore

  1. in_patternsにコピーしたい(=include)、ex_patternsにコピーしたくない(=exclude)ファイル/ディレクトリのglobパターンのリストを指定(どちらか1つでもよい)
  2. CopyTreeIgnoreインスタンス化 (IgnPtn
  3. shutil.copytree()ignore引数にIgnPtn.ignore()を渡す

すると、shutil.copytree()に指定したsrcディレクトリを再帰的に探索し、include/excludeの設定を反映してコピーされます。ちなみにcopytree()深さ優先探索です。再帰だから当たり前かもしれませんが。

補足説明

ignore関数

ignore指定する場合、公式ドキュメントにはignore_patterns()を使用してコールバック関数を生成せよと書いてあります。例えば*.txt, *.jpgにマッチするファイルを除いてディレクトリをコピーしたいとき、

# 無視するファイル、ディレクトリ名をglobパターンにて指定
# 可変長引数で受けるため、iterableな型は受け付けない
ignptn = shutil.ignore_patterns('*.txt', '*.jpg')
# src path
src = 'path/to/src/directory'
# dst path
dst = 'path/to/dst/direcotry'
# 引数にignptnを指定
shutil.copytree(src, dst, ignore=ignptn)

のように使用します。ignore_patterns()が生成する関数(ignore関数と呼ぶこととします)は、

  • 引数を2つ(str, list[str])とる
  • 無視するファイルのリスト(list[str])を返す

関数です。copytree()は対象ディレクトリを再帰的に探索し、それぞれのディレクトリ内でこのignore関数を呼び出します。ignore関数の戻り値に含まれるファイル・ディレクトリは無視してコピーします。

ignore関数をコードにおこすと、引数・戻り値は以下の通りです。

def ignore(directory: str, files: List[str]) -> List[str]:
    '''
    directory:再帰探索中のディレクトリの絶対パス
    files:direcotry内のファイル一覧
    戻り値:無視するファイルのパスのリスト(相対パス指定する場合はdirectoryを基準とする)
    '''

ignore_patterns()によるignore関数は「files内を探索し、指定のglobパターンとマッチする文字列を戻り値のリストに加える」ということをしています。

つまり、各ディレクトリ内でコピーしたいファイル以外のリストを返すような関数を書いてやれば、任意のファイルを除く/含むようにコピーすることが可能です。あとはそれっぽく実装するだけですね。

CopyTreeIgnore.ignore()はinclude/excludeを鑑みた結果、無視するファイル一覧をリストとして返しますが、重複がないようにsetによる集合演算を使用しています。「include設定によって無視されるファイル群」(_create_include_set()で生成)と「exclude設定によって無視されるファイル群」(_create_exclude_set()で生成)を求め、和集合を取っています。別に重複のせいで著しくパフォーマンスが落ちることはないと思いますが、なんとなくすっきりするので。

callbacks引数

CopytreeIgnore()callbacksという引数を取るようにしています。この引数に関数オブジェクトを与えてやることで、ignore()とは別にディレクトリを探索する毎に任意の関数を呼び出せるようにしています。まあinclude/excludeでほぼ事足りるのですが、ちょっとトリッキーなことをしたいときに別関数を実装して引数指定してやることで拡張可能にしました。自分の場合、

  • 同名の別拡張子ファイルが同ディレクトリに存在している場合、片方だけをコピーする
  • 特定の階層以下を無視する(ignore指定では名前が競合する場合などに有用)
  • 他ソフトの動作待ちのために適宜sleepさせる
  • コピーのログを出力する

みたいなことをしたかったので。。

まとめ

shutil.copytree()の引数ignoreに渡すコールバック関数を拡張したクラスCopyTreeIgnoreを実装しました。shutilでも意外と痒いところに手が届かないことがあるんですね。そもそもshutilはファイル操作の為の関数群なので多くを求めすぎかもしれませんが…

自分の資産は自分で守る。『教養としての投資入門』

学生の頃までは「将来性があって技術的なことができれば給料はそこそこでいいや」とか「貯蓄とか一般財形しとけばいいでしょ」と雑な考えだったのですが、少し働いてみて気が変わりました。労働まじでしんどすぎ。働くのが嫌になったので気分転換に以下の本を読みました。

「教養としての」と名のある通り、給与を支給される全ての労働者が知っておくべき内容でした。しんどい労働で蓄えた財産を「経済」とやらに理不尽に減らされないためにも、投資はするべきです。

著者

ミアン・サミ氏という投資家(?)です。wikipediaに載っているぐらいには有名な人っぽいです。

ミアン・サミ - Wikipedia

医療工学・電子工学・経済学を専攻し、証券会社に勤めつつ様々な事業を興して色々ありながら執筆や講演活動をする異色の経歴って感じです。25歳で6000億の資金運用を任され、30歳で事業に失敗して破産寸前になるも現在では別事業で軌道に乗せてることからも資産運用にはかなり強そうです。ブロックチェーン技術の発展と普及にも携わっているとのこと。すごい(こなみ)

概要

  • 投資とは未来予測であり、今のお金と時間を入力として未来の価値を受け取る行為である
  • 生涯収入は三つの要素で決まる
    • 自動投資
    • 楽しむ投資
    • 教養投資
  • 自動投資とは老後資金のために行うものであり、長期で実施することで確実に資産を増やすことができる投資である
  • 楽しむ投資とは試行錯誤の中で学びを得ながら実施することで、爆発的なアウトプットをもたらしうる投資である(投機とは別物)
  • 教養投資とは生涯賃金を上げるための投資であり、今の時間と引き換えに自分が解決できる問題を増やす

多くの経済学者や投資家の理論に基づき、筆者の経験もミックスして上記の三つの投資(バル投資と呼ばれています)が提唱されています。

自動投資

自動投資はすぐに始められる上に、長くやればやるほどよいものとされています。一言で言えば「毎月自動的に、有名投資家がやってる割合で金融商品に投資する」と言うものですが、以下の考えに基づいています。

  • 経済には4つの季節があり(一般に景気とか言われたりする)、季節によって価値が上がる資産、下がる資産が存在する
  • 現金による預金一択では経済の季節による影響を大きく受ける博打であるため、適切に分散したほうが良い
  • 年率1~2%のインフレを目標にコントロールされるため、現金の価値は長期的には下がっていく
  • 人々が得意なことを仕事にして自由に取引する限り、経済は長期的には必ず成長する(国富論

特に最後が重要で、自動投資の根幹な考えになります。長期的には必ず国は豊かになって行きますが、現金の価値は相対的に下がっていくため、運用が必要になるわけです。

といっても素人が一から投資対象を考えるのは無理なので、

  • 有名投資家や機関投資家と同じ資産配分で
  • 値動きを気にせず自動的に投資するような仕組みを作る

ことが大切だと述べられています。本で紹介されている資産配分は経済の4つの季節のどれにも対応できるものであり、不況下でも資産価値を守ることができるとされています。

楽しむ投資

楽しむ投資は一般的によく言われるような投資です(投機とは明確に別だと述べられています)。いわば「ゼロサムゲーム」であり、自動投資のように必ず資産を増やせるものではありません。むしろプロの投資家と同じ土俵で戦うことになるため、素人が安易に手を出しても高い確率で負けます。メインディッシュはあくまで自動投資であり、サイドメニューとして楽しむための投資として取り組むことが原則です。

楽しむ投資をする上では以下が大切だと述べられています。

  • 脳の癖を知ること。人間は心理学的に自分に都合の良い考えをする傾向にあるため、それらを把握して冷静でいること。
  • 金利を正しく理解し、現金・株式・債権・実物資産のアセットクラスの特性を知ること。

上にあげたのはあくまでもスタートラインに立つための知識です。他にも勝率を上げるために経済の季節あてや日記によるフィードバックなどが紹介されています。実際に楽しむ投資をするときは短絡的な勝った・負けたではなく、記録をつけた上でやるのが良さそうです。

教養投資

教養投資とは生涯賃金を上げることであり、それはつまり「より多くの問題を解決できるようになる」ことです。人は問題解決の報酬として給与を得ているので。

基本的に教養投資では時間を投資します。そのためには以下に気をつけます。

  • やらないことを明確にする。特に

    • 社会全体のメガトレンドに逆らう
    • 自分の性格スキルに逆らう
    • 「知性」「エネルギー」「誠実さ」のない人と仕事をする

    のは避ける。

  • 一つの分野でトップになるのではなく、スキルを組み合わせて希少価値を出す。そのためのスキル習得を行う。

  • レバレッジを活用する。自分一人でやれないことはお金やチームに頼ってリターンを大きくできる。

まあ他のビジネス書でもよく言われるような事柄かもしれません。特段目新しさはないですが、興味深いのは「性格に合わないことはやらない」と言い切っているところです。本書でビッグファイブ理論に基づく性格診断というのを取り上げており、これに基づいて自身のスキルスタックを伸ばしていくという戦略をとっています。「性格次第では詰みです」と言われている気もしなくもないですが…

教養投資の良い点として「失敗しても資産が残る」ことがあげられます。実際に損をするわけでもなく、経験として身になるため自動投資と合わせて積極的に実施していくべきでしょう。

実践

とりあえず以下のことを実践してみようと思います。

自動投資する

本に書いてることをそのまんまします。つまり株式(投資信託)と債権と実物資産に毎月定額で分散投資します。おわり。

あまりにも簡単すぎてやってる感がないですが、この本を読んで得られた一番大きい価値かもしれません。

税務・会計を知る

スキルスタッキングの取り組みとして、社会人として知らないと損をする類の税(本書では無知税と呼ばれています)を把握し、会社の業績や状況などを財務諸表などから読み取れるようになりたいです。そのためにまず適当な本でも買って勉強するか。そもそもサラリーマンなのにお金の話知らなさすぎなんだよな。。

万人が身につけるべきスキルとして、他にも第二言語の習得などがありますが、当たり前すぎるのでここでは別のアクションを取り上げました。

まとめ

投資についての基本を学びました。本書でははお金の話だけではなく、教養投資のような概念についても学びを得ることができます。

自分は組み込みソフト開発に従事しているので、ついソフト開発についてのスキルを深める方向ばかりに気を取られがちです(それももちろん大切)。ですが「問題解決によって価値を生み出す」ことを念頭におけばソフトの専門知識だけでは足りません。というか自分以上に専門に明るい人がいれば自分は用無しになってしまうので、差別化の意味でもスキルスタックは重要です。願わくばこの取り組みが生涯賃金や資産を少しでも高めて欲しいものです。

Googleドライブ起動中の右クリック(コンテキストメニュー)表示が遅いのを解消する

Googleクラウド同期ソフトである「バックアップと同期」や「Googleドライブ(旧Google Drive File Strem)」起動中、エクスプローラ上での右クリック(コンテキストメニュー)が開くのが遅くなると言う問題があるようです。

support.google.com

19年にissueとして挙げられていますが、まだ修正はされていないようです。とりあえず解決法を調べたのでメモします。ここではAutohotkeyを使用する方法を取り上げます。

確認した環境は以下。

  • Windows10 (2004)
  • バックアップと同期 == 3.54.3529.0458
  • Googleドライブ == 46.0.3.0
  • Autohotkey == 1.1.30.03

結論

Googleコンテキストメニューは使わない派

バックアップと同期では任意のフォルダを同期対象として設定できますが、フォルダ右クリック→コンテキストメニューから同期指定が可能です。この機能をほぼ使わないのであれば、Googleドライブ関係のコンテキストメニューを無効化することで右クリックの遅延を解消できます。

以下のregファイルを実行(結合)すると一発で設定できます。

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\shellex\ContextMenuHandlers\GDContextMenu]
@="-{BB02B294-8425-42E5-983F-41A1FA970CD6}"

[HKEY_CLASSES_ROOT\Directory\shellex\ContextMenuHandlers\DriveFS]
@="-{EE15C2BD-CECB-49F8-A113-CA1BFC528F5B}"

[HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\GDContextMenu]
@="-{BB02B294-8425-42E5-983F-41A1FA970CD6}"

[HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\DriveFS]
@="-{EE15C2BD-CECB-49F8-A113-CA1BFC528F5B}"

適当に名前を付けて.regの拡張子で保存すると結合できるようになります。DriveFS(旧Google Drive File Stream)の設定も含まれていますが、未インストールでも問題ありません。

コンテキストメニューは使いたい派

Autohotkeyを使用すると、「通常の右クリック時は素早くメニューを開くが、特定のキーを押下しながら右クリックするとGoogleドライブ操作も可能なメニューを開く」といったことが可能になります。以下記事で紹介されています。

superuser.com

とりあえず以下作者のスクリプトを実行しておけば、Googleドライブをコンテキストメニューから利用したいときのみ「Shiftキーをしながら右クリック」で対応可能です。

GitHub - patricknelson/google-drive-context-fix: Dynamically enables/disables Google Drive in context menu when right clicking in Windows Explorer

補足説明

Autohotkey

Autohotkeyとは、Windows環境のみで動くスクリプト言語です。名前の通りキーボードに独自のホットキーを割り当てられるものですが、現在では非常に多くの機能が実装されてスクリプト言語と呼ばれるほどになったようです。

www.autohotkey.com

やれることが多岐にわたるのでここでは説明しきれませんが、「アプリケーション毎にホットキーを割り当てたい」とか「特定操作を自動化したい」といったときに役立ちます。自分もちょこちょこ利用しているのでそのうち別記事書くかも。Windowsのみ対応なのが惜しいところですが、サーバでの利用よりも個人端末での作業効率化がメイン用途ですね。

実行環境をインストール後、スクリプトを実行するとタスクトレイに常駐します。今回の例で言うと、以下スクリプトを実行しておけばやりたいこと(右クリック遅延解消)はできます。

GDrive-context-menu

標準エクスプローラ以外を使用している場合は、explorer.exeの部分を使用しているアプリのファイル名に変更します。

レジストリ設定

エクスプローラ上の右クリックはファイル、フォルダの二種類を取るので、それぞれの設定を変更する必要があります。右クリックに関する設定は以下の記事が参考になります。

www.atmarkit.co.jp

上記の方法ではregファイルで一括設定できるようにしています。regファイルについては以下記事が参考になります。

www.atmarkit.co.jp

と、ここまで書いてから右クリックのレジストリ設定についてめちゃくちゃ詳細に書いた記事が見つかったので載せときます。Autohotkey使ってたけどぶっちゃけこれでいいわ。。

qiita.com

まとめ

レジストリは可能性無限大な割に意外と設定に関する記事とか少なくて苦労します。Windows使いなら自分のこだわり設定を探求しても面白いかもしれません。(途中でいい記事見つけたので書く気が失せましたごめんなさい...)

固定小数点数による分解能表記(LSB)と演算

組み込み開発時に、一般的な(?)プログラミングではあまり扱わない要素として「固定小数点数」の利用があります。組織によっては単に「LSB」とか呼ばれたりしますが、新入社員の時にさも当然のことのように言われて困惑した思い出があるので、ここにメモとして残しておきます。

固定小数点数

おそらくプログラミングに触れて最初に知ることになる小数の表現方法は「浮動小数点数」だと思います。これは仮数部と指数部からなる表現方法で…というのは大体の教科書に書いてると思うので省略します。固定小数点数の説明は以下です。

固定小数点数(こていしょうすうてんすう、英: fixed-point number)は、小数点が置かれる桁を固定して表された数のことで、コンピュータ上で小数を表現する方法として使用される形式のひとつである。ある桁数のうちのある場所に小数点が固定されているもの(固定小数点)として扱う方式であるため、表現される仮数部に対して小数点の位置が移動する浮動小数点数の対義語として用いられる。すなわち、「固定-小数点数」ではなく「固定小数点-数」である。

演算自体は整数型と同じ方法、同じハードウェアで行われ、小数点位置は設計者の意図によって決定される

固定小数点数 - Wikipedia

絵的には下のサイトがわかりやすいかもしれません。

www.hdlab.co.jp

例えば、以下のような2進数があったとします。

11010011

これは整数型だと、十進数で211ですが、「この桁から下は小数点とします」と設計者が決めると固定小数点数となります。下位2bitを小数点以下とした場合、

110100.11

となり、この場合52.75と表現できることになります。データ自体は変わっていないため、「(同じ単位での固定小数点同士なら)演算自体は整数型と同じ方法でできる」というのが大きな特徴です。ざっくりまとめると、以下のようなメリットがあります。

  • 必要最小限のサイズで小数を表現できる(上の例では1byte)
  • 浮動小数点数より高速に演算できる

ただし当然ながら、以下のデメリットがあります。

  • 異なる単位での固定小数点数同士で演算できない
  • 小数単位は設計者が決める必要がある
  • 取りうる値から、設計者が変数サイズを決定する必要がある

特に二つ目は重要で、設計資料等が残っていないとただの整数型と見分けがつかないので、どういう数値を意味しているのかがわからなくなります。固定小数点数はA/D変換後の物理値などによく使われますが、上の例がもし電圧[mV]を表していた場合、211[mV]52.75[mV]はえらい違いです。コード中にコメントを記せばある程度緩和できますが、あくまでただのコメントでしかないため設計資料とのトレースが取れる状態を保つことが重要となります。1

単位としての「LSB」

「この変数は下位2bitを小数とします」と設計書に書くのは些か不格好なので、ちゃんと表記を統一したいところです。ここで「1bitで表現できる数」を考えてみると、

110100.11

は最下位bitは0.25を表すことになります。上の数値を10進数で読み、これに最下位bitが表す数値をかけることでも小数を求めることができます。(211 * 0.25 = 52.75)

この「最下位bitが表す数値」に単位をつけてしまうことで、より汎用的な表現とすることができます。ここで便宜的にLSBという単位を導入すると、上記の数値を物理込みで

0.25 [mV/LSB]

のように記述できます2。こうすることで、「この数値は最下位bitで0.25[mV]を表す」ということが簡潔に表現できます。計算上は

211[LSB] * 0.25[mV/LSB] = 52.75[mV]

のように書けるので、単位系的にもわかりやすくなります。この[mV/LSB]という単位、対象の物理量によって分子部分が変わるので、読む時は単に「分解能」「LSB」のように呼ばれたりします。LSB(Least Significant Bit)はバイトオーダーについての説明でよく出てくる単語ですが、組み込み系では開き直って単位名として使われるわけですね。会話の中では「分解能が xxx LSB」とか「1LSBでxxx mV」とかいう言い回しをすることが多いようです。

この場合は2の冪乗が分解能となっていますが3、物理値は本来連続なので2の冪乗である必要はありません。設計次第では以下のような定義も可能です。

0.777 [mV/LSB]

また、場合によってはOffsetと呼ばれる偏差を導入することもあります。Offsetはそのままの意味で「原点をずらす」用途で使われます。Offsetについては以下が参考になります。

algorithm.joho.info

例えば分解能を0.25[mV/LSB]、Offsetを-100mVと指定すると、

211[LSB] * 0.25[mV/LSB] - 100[mV] = -47.25[mV]

となります。Offsetは基本的に計算後の物理量と対応するため、単位も物理量と同等のものをとります。データが取る範囲は1byteで十分表現できるけど、絶対値が大きい場合にはOffsetが役に立ちます。当然これも設計者が決めるため、ソースコード中には含まれない情報です。

ここまでの説明で、ちゃんと物理を勉強してきた人とかは

『分解能に単位がつくのはおかしい!』

『計算上の数値はただの数値であり、物理量ではない!』

という疑問が湧いてきたりするのですが、組み込み開発ではこの表記がデファクトスタンダードとなっているようです4。組み込み業界で生きるなら諦めて慣れましょう。

演算時の注意

固定小数点数の概要と表記について説明できたので、これですぐ使い始められる…と思いますが、浮動小数点数の演算と比較して留意すべきことがあります。除算とデータサイズです。

除算

固定小数点数は整数型演算で処理します。ゆえに、除算は自動的に切り捨て除算となります。必然的に分子<分母となるような演算は0になってしまうので、演算の順序には気をつけなければなりません。

また、浮動小数点数なら多くの場合用意されているROUND()も、自分で計算する必要があります。

// 切り捨て除算
result = a / b;

// ROUND
result = (a + (b/2)) / b;

分母の二分の一を分子に足すことで、ROUND演算ができます(負の数は分子から引きます)。計算自体は単純ですが、除算する箇所で全てこの式を書くのは煩雑になります。やるとしたら以下のような関数を利用したくなりますが、

// ROUNDを実施する除算(正数のみ想定)
long fp_number_div_round(long a, long b){
  return (a + (b/2) / b);
}

この関数では「引数と戻り値がlong型固定」になってしまいます。C++ならオーバーロードという手段が取れそうですが、Cでは適宜キャストしてやる以外の解決方法がありません5。(何かいい解決策あればご指摘お願いします、、)

データサイズ

演算結果がデータサイズにおさまっても、演算途中でオーバーフローしてしまうと意味がありません。オーバーフローが起こるのは浮動小数点数でも同様ですが、任意精度演算をサポートするような言語に慣れた方は注意が必要です。データサイズは以下のように、乗算・加算で広がる感じです。

uint8_t a;
uint8_t b;

// 8bit * 8bit = 16bit
result = a * b;

// 8bit + 8bit = 9bit
result = a + b;

基本的には型が取り得る最大値・最小値を考慮した設計とすべきですが、センサなどの仕様で入出力の定義域が分かっている場合はこの限りではありません。

実例

固定小数点数を使用した演算の実例です。単純な1次遅れのLPFをソフトで実現しようとした時、後退差分による離散化を利用することが多いと思います(後退差分による離散化はここを参照)。今回値の入出力を x[n], y[n] 、サンプリング周期と時定数をそれぞれ T, \tauとおくと、出力は

{\displaystyle
y[n]=
\frac{T}{T+\tau}x[n]
+\frac{\tau}{T + \tau} y[n-1]
}

で求まります。この式を浮動小数点数の演算と同じように書くと、以下のようになったりします。

// x: 入力
// T: サンプリング周期
// tau: 時定数
// y: 出力
// y_Z: 出力の前回値

// 1次LPF
y = (T / (T + tau)) * x + (tau / (T + tau)) * y_Z;

どこが問題になるでしょうか?

まず一つ目に、除算によって各項が確実に0になるため、出力yは常に0になってしまいます。このような場合、通常は交換法則を利用して、分子の乗算を先に計算します。

// 第一項の計算
tmp = (T * x) / (T + tau);

このような演算に置き換えます。(T * x)>(T + tau)が設計上、常に保証されるならとりあえずこれで計算できますが、これでもまだ不十分です。除算は

  • 切り捨て(良くて四捨五入)なので精度が粗くなる
  • 一般に乗算より遅い

ため、できるだけ避けたい演算です。と言うことは

 {\displaystyle
y[n] =
(T \cdot x[n] + \tau \cdot y[n-1])
\frac{1}{(T + \tau)}
}

という式にした方が良いことがわかります。

// 除算回数を減らすため、分子を先に計算
tmp = T * x + tau * y_Z;
// 出力を計算
y = tmp / (T + tau);

ここで問題になるのが、T * x + tau * y_Zのとる範囲を予め計算しておく必要があると言う点です。データサイズの節で述べたオーバーフローですね。処理系が16bitとかだと簡単に超えますし、32bitでもlong型を多めに使うようなAD値とかだと意外と油断できません。

オーバーフローの可能性がある場合は、

  • 除算を先に実施する
  • 適宜リミット処理を設ける

などの対応が必要です。固定小数点数を使用する場合はシステムとしての入出力と演算を考慮して設計を行うべきでしょう。

まとめ

固定小数点数についての分解能表記と演算時の注意について述べました。明らかに人間にとっては読みづらい表現なため、制約が厳しい一部の組み込み開発以外ではまず使うことはないでしょう…。普通にプログラミングに触れるのではおそらく知る機会がないので、「リソース制約がある環境での開発が具体的に何を意識しているのか」を知りたい人の参考になれば幸いです。


  1. コメントだけで管理してると簡単にデグレする(実体験)

  2. 一般的な表記ではないですが、個人的には一番わかりやすいと思ってます

  3. 実際、量子化の都合上そういうケースの方が多い

  4. 某大手R社のマイコンデータシートにもLSBの表記が使われています

  5. 引数・戻り値をその処理系が取りうる最大サイズ(long longとか)に指定しておけば汎用的に演算すること自体は可能ですが、言わずもがなリソースは食います

初心者から脱したいなら「C言語 ポインタ完全制覇」

新卒のとき、派遣で来てた人の机の上に下の本がありました。

その時はひよっこ中のひよっこだったので「なんか難しそうな本だな」と思ってスルーしてましたが、この度理解を深めるために読んでみました。一言で言えば「もっと早く読んどけばよかった」です。C言語を扱う脱初心者したい人はかなり参考になる部分があるかと思います。

著者

前橋和弥氏が執筆されています。名古屋市ソフト会社で働いているようです。他にもCやJavaなどについての多数の著書があります。この本のコラムなどからもなんとなく感じ取れますが、各言語の言語仕様やアルゴリズム・データ構造についての造詣が深い方だと思います。

概要

かなり色々なことが書いてありますが、メインはやはりポインタに関する説明です。説明されているトピックの中で、特に以下は学びになりました。

  • ポインタと配列について
    • 配列は式中ではポインタに読み替えられる
    • 添字演算子[]の意味
  • スタックについて
  • Cの文法
    • 複雑な宣言・型の読み方
    • 「配列の配列」による多次元配列の実現
    • 値渡しと参照渡しの違い(Cには値渡ししかない)
  • 実用的な使い方(定石)
    • 動的配列、可変長構造体の利用
    • 連結リスト、木構造などデータ構造の実現
    • スコープを意識した実装
    • ヒープ利用時のデータ構造の選択

個々の詳細についてはここでは説明しきれませんが、上記の内容についての知見は得られます。

すでに発売されてから時間が経ち、かなり有名な本なのでネット上に関連記事も豊富にあります。例えば「ポインタと配列の読み替え」については、以下の記事で述べられている内容が参考になります。

ポインタと配列の微妙な関係 - めもめも

「複雑な宣言の読み方」に関しては、以下記事がそのまんま書かれている感じです。

やり直しC言語:複雑な宣言の読み方: Architect Note

最初読んだ時は「そんな複雑な宣言、普通書かねえよ!」という感想が真っ先に浮かびましたが、理解できるようになると結構スッキリします。これらの内容に興味を持てるなら買って損はないでしょう。

またスタックとかアドレスの使われ方を理解すると、以下記事の現象の原因と解決策を提示できるようになります。

[C言語]スペースを大量に入れるとバグが直るコードを書いてみた | 右や左の旦那様

こういうのがデバッグできるようになると、成長の実感が湧きますね。

感想

内容的には非常に濃いので、繰り返し読んで理解を深めていくのが良いかなと思います(アウトプット用に記事を書こうとしたもののボリューム多すぎて放棄した)。ただし、標準入出力や文字列操作に関するライブラリとかは知っている前提で説明される箇所もあるので注意が必要です。自分は業務では標準入出力とかほぼ使わないので、読み解くのに少してこずりました。

ぶっちゃけポインタと配列の違いとか理解しなくても「動いて仕様を満たすコード」は書けると思いますが(そして設計よりプロセスを重視するような現場ではそういった人の方が多いわけですが)、C言語を使うソフト開発者としては知っておくべき事柄が満載です。常に好奇心を持って開発したいものですね。

コンサルじゃないけど『コンサル一年目が学ぶこと』を読んだ

Amazon Readingで読めるビジネス書で、評価の高かった本を読んでみました。コンサルに関わらず、どの業界でも使える仕事の基礎みたいな内容です。

コンサル一年目が学ぶこと

コンサル一年目が学ぶこと

著者

外資コンサルで働く大石哲之氏が執筆されています。自身の経験に加え、実際にコンサルタントとして働いた経験を持つ方々からの取材と議論によって内容を推敲されているようです。

本書の他にもロジカルシンキング系の本を執筆されています。調べてみるとブロックチェーン関連の著書や取材記事も見つかったので、ITやFintech分野に精通しているのかもしれません。ですが本書ではITに関わらず、広く一般に通じるような内容が取り上げられています。

概要

大きく4つの技能を章に分けて紹介し、その実例や細かいtipsを説明する形で記述されています。ざっくりまとめた内容は以下です。

話す技術

  • 結論から、端的に話す。YES/NOで答えられるものはまず答え、その後に理由を述べて問題の所在を明らかにする。
  • ファクト(数字と論理)で語る。論理と数字は共通の言語であり、どんな人間にも通じるローコンテキストな基準である。
  • 相手に理解してもらえるように話す。相手は無知識であることを前提とし、適宜反応を見ながら最も伝わりやすい(相手に合わせた)方法/形式で伝える
  • 相手の期待値を把握する。意思疎通しながら期待値を推し量り、無理な要求は退けながらも、常に期待値を超えた成果物を出す

思考術

  • 手順を考えてから着手する。作業全体の道筋を考えてから関係者に合意を取り、手順に基づいて細部の作業を進める。
  • ロジックツリーを使用して対象を分析する。論点を分解した後に数値による分析を行い、重み付けをして次に取るアクションを決める。
  • 提案するときは事実・解釈(仮説)・行動を区別して説明する。
  • 仮説→検証→フィードバックのサイクルで仮説の確からしさを証明し、意思決定に繋げる。そのために、常に考えながら情報に触れる(単に情報量を増やすという策を取らない)。

デスクワーク技術

  • 議事録は決定事項、確認事項、次に持ち越すこと、次までにやることを明確にする。
  • 説明資料作成時は、根拠となる事実+解釈が1セットとなるようにする。特にパワポは1スライド1メッセージを守る。
  • PCやツールの操作速度を上げる。ショーカットなどで作業速度を上げることで、思考に費やせる時間を増やす。
  • 重要な部分を見極め、残りを捨てる。情報収集や検討などは、重要だと思われた箇所について深く実施する。
  • 課題管理をする。直面している課題を共有し、役割と期限を決めてプロジェクトを進める。

プロフェッショナル・ビジネスマインド

  • 相手(顧客や会社)に価値を提供する。相手にとって価値があると思わなければ自己満足である
  • 自分には金銭的コストがかかっていることを自覚する。会議で発言しない、休憩時間を余分に取る等の行為はプロとして不適切である。
  • 拙速は巧遅に勝る。完璧なものを時間を掛けて提出せず、大まかな成果物を早く出し、改善するサイクルを回す。
  • (頑張りや評価ではなく)仕事の成果に対してコミットする。顧客を起点とし、約束を果たすことを最優先として行動する。
  • 「こうなりたい」と思える人物を見つけ、徹底的に真似る。
  • リーダーの指針に積極的に賛同し、周りに行動を促すフォロワーシップを発揮する。
  • 他とは違う役割を果たしながらチームに貢献する。チームワークとは分業であり、自分の今の能力でチームに貢献できる方法を考える

要点

要点と感じた部分について書きます。

期待値をコントロールしつつ、超えていく

自分が真っ先に「あ、これはできてないな。。」と感じた部分です。なんやかんやでコンサルは社会的に信頼度の高い業種であり、評価と高給を得ている理由とされる部分ではないかと思います。無理な要求はきっぱり断りながらも、相手の期待値をコントロールし、成果としてはそれを超えるように働く。そのためには、

  • 仕事の目的
  • 成果のイメージ
  • クオリティ
  • 優先順位(緊急度)

を確認することが良いと述べられています。(これは指示を出す側も意識しておくべき)

相手が明確なイメージを持っていない場合は、2章で述べられている仮説・検証で成果物のすり合わせをすることになります。

仮説ドリブンで動く

第二章は参考になる部分が多かったのですが、全体としては「仮説」がより重要であるように感じました。仕事の手順を考えるのも、解釈から行動を提案するのも、すべて仮説がなければ始まりません。(人は大なり小なり先のことを予測しながら生きていると思うので)

この「仮説を立てる」という行為、学生時代では研究で当たり前にやってきたのに、仕事となると「とりあえず動く」みたいな行動をとりがちな気がします。おそらく納期などの外部要因がそうさせるのだと思いますが、結果的にどちらが早く仕事が終わるのかは明らかでしょう。

20:80の法則を意識する

物事は20%の本質部分を抑えれば、80%の効果を出せるという法則です。元は経済学の用語ですが、「重要な部分以外は切り捨てる」という思考はこれを元にしています。あらゆる業務(特にソフトウェア開発)についてこれが有効かは不明ですが、少なくとも対人折衝的な業務においての資料作成、情報収集には当てはまりそうです。

自分の得意分野で価値を提供する

「チームワークとは分業である」という主張にハッとさせられました。ソフトウェア開発において、誰かがいなくても代えが効くようにプロセス設計されていることが多いですが、「全く同じ役割を担う者は二人もいらない」と本書で述べられています。そのチーム内で、どのように価値を出すかを自分の得意分野と相談しながら考えることが大切だそうです。場合によって、それは「高い設計能力」かもしれないし、「誰からも好かれる人当たりの良さ」かもしれません。大事なことは、価値を出せるなら手段は何でもよいということです。

実践

今日から実践したいことを書きます。

考察しながら情報に触れる

仮説ドリブンや20:80の法則を実践するためには、「すぐに仮説を立てる」ことや「重要な部分を素早く見つけ出す」ことが求められます。そのためには日頃から多くの情報に触れ、そこから意味のある考察をし続けることが重要だと思いました。

とりあえず身近な例として、

  • Twitterで見つけたニュースの見出しに対して、背景や原因を考えてから記事を開く
  • 広告やCMなど、そのデザインやメッセージに至った背景を考える

みたいなことから始めます。

まとめ

典型的なザ・ビジネス書みたいな内容でしたが、やはり仕事の進め方的な部分でコンサルの方から学ぶことは多いと感じました。本書で取材元とされた方々の経歴を見ても、やはり一般的に優秀であることは疑いようのない事実です。(あくまで一般的に見てですが)

コンサルの言うことを鵜呑みにして無茶な主張をする上司の気持ちも少しはわかr…いややっぱわかんねえな…いつかわかるようになるのかな…

全ての基本は構造化から。『組み込みソフトウェア開発のための構造化モデリング』

構造化モデリング及び設計に関する本を読みました。オブジェクト指向設計が提唱されて久しいですが、組み込みの製品開発では今でも構造化設計に基づく部分が多くあります。(C言語だと言語仕様的に実装しにくいということも一因だけど、新規開発が少なく実績優先される製品だとその傾向が強いので)

オブジェクト指向を使う前に、今一度基本に立ち返って古典的な手法を学ぶことも大切だと思わせてくれる一冊です。

著者

SESSAME(組み込みソフトウェア管理者・技術者育成協会)というNPO法人の方々が執筆されています。文字通り、日本の組み込みソフトウェア開発の技術者を育成するための教材開発や研究を行っている団体です。「単に技法を紹介する」という内容ではなく、沸騰ポットの事例に対してSESSAMEのメンバーが実際に構造化設計を試し、得られた教訓やTipsなどをまとめた内容になっています。初版が2006年と少し古いですが、今でも基本となる設計原則が書かれているため、新人への教育資料としては最適かと思います。

本の概要

モデリングの利用

組み込みソフトウェア開発では枯れた(実績がある)技術が使われることが多く、現代的なプロセスや開発手法が使われることが少ないと問題提起をしています。本書では曖昧な要求から「要求モデリング」「分析モデリング」を逐次繰り返し、検討した内容を「設計モデリング」へのインプットとして最終的な成果物(ソースコード)を作成することを推奨しています。今では「アジャイル」という名前で体系化されていたりしますが、要はウォーターフォールによる一方向の開発では無理があるという主張です。

要求モデリング

システムが「何をして欲しいのか」と、その結果「何が起こるのか」を定義します。そのための手段として、本書ではイベントリストの作成が挙げられています。イベントリストは

  • 動作のトリガーとなる事象(イベント)
  • イベントが生み出す情報(スティミュラス)
  • スティミュラスによって開始される動作(アクション)
  • アクションの結果、出力される情報(レスポンス)
  • レスポンスによって生じる外界への影響(エフェクト)

を記述することにより、システムの本質と利用者の要求を洗い出すものです。これにより機能要件が抽出されます。

機能要件を満たすためには品質やリソースが制約条件として出てきますが、ここではそれらを非機能要件として別管理します。システムの本質や要求を見失わないようにするためです。また検討段階でタイミングに関する要求が出た場合、タイミング仕様としてこれも別管理します(組み込みシステムにおいて、タイミングは優先的に考慮されるべき要件のため)。

他にも用語の定義などがありますが、大まかには

  1. イベントリストの作成(機能要件の抽出)
  2. 考慮されるべき非機能要件の抽出
  3. タイミング仕様の抽出

の3つで要求モデリングの成果物とします。

分析モデリング

開発対象となるシステムのスコープを決め、入出力を定義するフェーズです。以下のような流れで進めます。

  1. スコープを定義する 要求モデルを入力としてコンテキストダイアグラムを作成する
  2. 機能を分割する コンテキストダイアグラムから、より具体的なプロセス(処理単位といった意味合い)に分割し、データフローダイアグラム(DFD)を作成する。
  3. プロセス毎の動作仕様を決める 機能分割した各プロセスの動作仕様を決定する。

構造化モデリングにおいては、コンテキストダイアグラムはUMLでいうところのユースケース図に近い役割を担っています。外部エンティティとのデータの受け渡しを中心に考え、各プロセスを詳細化していくことでDFDを作成します。

最も重要なのは、HOWではなくWHATを中心に考えることです。つまり実装手段を考慮していくのではなく、何を作りたいかを意識します。その過程でWHYという疑問にぶつかったら、分析モデリングで要件を再定義します。(状態遷移図やディシジョンテーブルといったツールを適宜使用することが推奨されていますが、制御フローに関わる部分は極力DFDには含めないように留意します)

設計モデリング

分析モデルで得られたDFDのプロセスを、どのように実現するかを考えます。主に機能分割に沿ったモジュールの作成という作業となります。ここでいうモジュールは再利用可能な処理単位であり、再利用可能とするためには以下に留意する必要があります。

  • モジュールが提供する機能を単一にする(単一責務)
  • 実装とインターフェースを分離する(情報隠蔽

設計モデリングは以下のような流れで進めます。

  1. DFDから構造図を作成する STS分割やTR分割といった手法でDFD内のプロセスをモジュールに分割する。
  2. 局所的に作成した構造図をマージし、システム全体の構造図を作成する システム形状を見直しながらモジュールの統合・分割を進める
  3. タイミング要求に対応する 要求モデルで作成したタイミング仕様を満たすように詳細設計する

モジュール分割・統合の際は、最大抽象点がどこになるかを意識します。入力データが種々の変換を経て抽象度が最大となる点を最大抽象点と呼び、そこを機能単位での制御モジュールとして扱うことで、データ毎の独立性が高い処理となります。

設計品質

品質特性として、以下に留意しながら設計を行うことが大切です。オブジェクト指向設計でも当たり前に使われる

  • モジュール分割
    • 単一機能・責務となるまで分割する
    • 重複のないモジュールを作成する(共通化できる部分を探す)
    • 実装は隠蔽する(モジュールが何を提供するかのみ公開し、その実現手段は外部に公開しない)
  • 凝集度(コヒージョン)
    • モジュールが単一機能で構成されているかどうかを図る尺度であり、高いほど良い(7段階ある)
    • 中心となるデータがなく、順序的な関連性から一つのモジュールを構成している状態(手順的コヒージョン)は凝集度が低く、避けるべきである
  • 結合度
    • モジュール間の依存関係を図る尺度であり、低い方が良い
    • 制御を意図した情報(フラグなど)が伝達される状態(制御結合)は結合度が高く、避けるべきである
  • システム形状
    • ファンインが多いモジュールは凝集度が高くあるべきである
    • ファンアウトは7程度に抑え、それ以上多い場合は分割を検討する
    • システム階層の上下は「論理 - 物理」で分離されるべきである
    • システムへの入力は上位(論理)層へと抽象化しながら伝達し、上位で判断された上で下位(物理)層へ流してシステムの出力とする構造を目指す

要点

なんとなく感じ取って重要だと思った要点をまとめます。

要件の優先度とステークホルダを明確にする

要求モデリングでは一般的には要件定義と呼ばれるフェーズに相当するかと思います。機能・非機能要件を明確にするのは当然として、各要件の優先度とステークホルダ、(あとできれば対応マストな納期)を把握しておくことが大切です。限られたリソースで要求を実現するためにはある程度妥協しなければならない局面(組み込みではよくある)も出てきますが、妥協する箇所を決める上で重要となるからです。特に「やらなくても良い理由」を説明するためには要件同士の関係や順位を明確にする必要があります。

分析時点で実装手段に拘らない

構造の検討時点で機能実現の詳細な方法を検討してはいけないとされています。特にDFD作成時には分岐やループなどの制御構造に言及しないことが重要です。(CFDという制御を含めた図も本書で紹介されていますが、あまり一般的な記法ではない上に制御フローの定義が曖昧で管理できずにレガシー化の恐れがあります)

DFDはあくまでデータ中心な静的図なので、制御を表すなら他の手段を使ったほうがいいです。もっとも本書ではそこには言及されていませんが…

論理-物理な階層で分割(抽象化)し、かつ隠蔽する

組み込みだとマイコンや周辺IC操作のために種々のハードウェアアクセスを多用しますが、システム上位のレイヤはハードを意識しない設計とするべきです。開発段階ではデータシートとにらめっこしながら試行錯誤するのが常ですが、モジュール作成時点ではシステム上位でハード操作の手順を意識するような実装としてはいけません。下位(物理層)の操作は隠蔽し、上位はあくまで「こういう状態にしたい」「こういう出力を出したい」という要求だけ投げるようなモジュール分割とすると、階層分離と抽象化が実現できます。

実践

少なくとも今後は以下に気をつけようと思います。

静的構造で分析する

自分はよくレビュー時などに「こういう動作(処理)をするから、やりたいことが実現できます」といった動的な説明をしてしまいがちです。ですが動的な振る舞い(HOW)から検討してしまうと「こういう場合はどうなるのか?」といった具体的な事例からボトムアップ的に積み上げていくことになり、最終的な構造が歪になってしまう恐れがあります。(というかよくやります笑)

分析時にはDFDなどの静的構造図を使用する方が、最終的な構造がスッキリすることが多い気がします。適切に階層化すればDFDでもそこそこの規模のシステムが表現できるので、小〜中規模な設計変更やシステム開発では有用でしょう。ただそれ以上の規模になってくるとDFDでは限界があるので、場合によりUMLを使用することも視野に入れておきます。問題はUMLを理解できる人がチームにいないことだが

他機能に依存しない設計とする

情報隠蔽や低結合度とも関連しますが、「この機能はこういう振る舞いをするから、この設計とする」といった他機能の内部処理を知っている前提での設計は極力排除します。「当たり前だろ」と言われるかもしれませんが、種々の理由で依存しまくった設計となってしまうこともままあります。特にC言語だと、ファイル分割とかスコープ管理が曖昧なせいで依存を解消できなくなるケースがあるように思います。開発初期段階で強く方針を打ち出し、開発メンバと共有しておくことが大切です。結局はコミュニケーションですかね。

まとめ

構造化モデリングについての基本を学びました。現代のソフト開発のなんとか原則とかに通ずる部分はあるのかなと思っています。ただ組み込みでは信頼性を最重視される場面も多く、本書のような設計技法によってもたらされる可読性や保守性が犠牲になることもあります。今後ハードウェアの性能が上がっても、モノとしての単価を抑えたい経営層としてはリソース十分なMCUを採用する動機は薄いので、これはそう簡単にはなくならない問題だと思います。

複雑化や急な変更に対応するためにソフトウェア開発は発展してきましたが、組み込みソフトの開発には設計や開発プロセスとは別のところに改善点があるのかもしれません。設計手法の本を読んでたはずなのにどうしてこうなった。。