ろぐれこーど

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

tagsファイルをパースして関数一覧を取得する

プロジェクト内の関数一覧を取得したいとPLに言われたので、適当に方法を考えてみました。構文解析から愚直にやると地獄を見そうなので、今回はCtagsを利用し、tagsファイルをパースするスクリプトpythonで書きます。

  • Ctags == 5.8
  • python >= 3
  • C/C++のプロジェクトを想定(他言語でも設定変えれば適応可)

結論

Ctagsを入手します。以下からwindows向けCtags日本語版バイナリをダウンロードできます。

https://hp.vector.co.jp/authors/VA025040/ctags/

mac OSへの導入は以下です。

https://log.yostos.org/2017/05/22/installing-ctags-to-mac/

以下コマンドをプロジェクトのトップディレクトリで実行します。

ctags --excmd==n -R .

生成されたtagsファイルのある場所で、以下スクリプトを実行すると、関数一覧をresult.txtに抽出できます。(オーバーロードしている関数は1関数とみなし、重複分を含めない形で出力します)

funcs = []
with open("tags", mode="r") as tags_file:
    # result.txtに関数名一覧を出力
    out = open("result.txt", mode="w")
    lines = tags_file.readlines()
    for line in lines:
        symbol_info = line.strip().split("\t")
        func = symbol_info[0]
        # 関数のみ取得
        if symbol_info[-1] == "f":
            func = symbol_info[0]
            # 重複した関数は除く
            if func in funcs:
                continue
            # 新規関数はresult.txtに出力
            else:
                funcs.append(func)
                out.write(func + "\n")
    out.close()

補足説明あれこれ

Ctags

wikipediaの引用です↓

Ctags(英: Ctags)はソース及びヘッダ内にある名前のインデックス(又はタグ)ファイルを生成するプログラム。様々なプログラミング言語に対応している。言語に依存するが、サブルーチン(関数)、変数、クラスのメンバ、マクロ等がインデックス化される。これらのタグによりテキストエディタなどのツールで高速かつ容易に定義を参照できる。相互参照ファイルを出力でき、また名前についての情報を人が読みやすい形で列挙した言語ファイルを生成することもできる。

https://ja.wikipedia.org/wiki/Ctags

Ctagsが生成するtagsファイルを解釈できれば、軽量で限定的な機能しか持たないエディタでも手軽に定義元ジャンプが利用できるようになります。tagsは以下のようなフォーマットで1タグにつき1行ずつ記述されます。

tag_name <TAB> file_name <TAB> ex_cmd;" <TAB> extension_fields
記述 概要
tag_name タグ名(関数や変数など)
file_name 定義元ファイルまでの相対パス
ex_cmd ファイル内の定義位置までジャンプするためのコマンド
extension_fields exコマンドに付随するkey-value形式のコメント

extension_fieldsにはオプション次第で種々の情報が含まれますが、今回はkindで示されるタグtypeを参照して関数の定義のみを取得します。プロジェクトのトップディレクトリで以下コマンドを実行し、tagsファイルを生成します。

ctags --excmd==n -R .

--excmdにより、tagsファイルに記述されるex_cmdの内容が変わります。n(number)を指定すると行数のみで定義元へジャンプするようになり、tagsファイルのサイズを最小にできます(その分ソースファイル編集するごとにtagsを更新する必要があるという欠点もあります)。今回は関数一覧を取得したいだけなので、ファイルサイズが小さくなるようにnを指定します。-R再帰的探索のオプションです。

extension_fieldsに含まれるkindはタグの種類(関数、変数、メソッド、クラスなど)を示してくれるので、これを使えば任意の種類のタグが取得できるようになります。タグは言語によって異なるため、以下コマンドで確認できます。

ctags --list-kinds=<言語の名前>

例えばC言語のタグは以下です。(C++も同じ結果です)

$ ctags --list-kinds=c

c  classes
d  macro definitions
e  enumerators (values inside an enumeration)
f  function definitions
g  enumeration names
l  local variables [off]
m  class, struct, and union members
n  namespaces
p  function prototypes [off]
s  structure names
t  typedefs
u  union names
v  variable definitions
x  external and forward variable declarations [off]

関数定義はfで表されるので、上記のスクリプトではこれをキーとしてタグを判別しています。この部分を変更すれば他タグも取得できますが、key-valueペアとなるタグtypeもあるため適宜書き換える必要があります。

Ctagsの詳細な使用方法やtagsファイルのフォーマットは以下のドキュメントで確認できます。

http://ctags.sourceforge.net/ctags.html

まとめ

tagsファイルを使用して関数一覧を取得しました。普段はVSCodeを使用しているのでCtagsは使ったことがなかったのですが、意外な使い道を見つけられて嬉しいです。

elxrリンカにおけるリンカスクリプトの記述

GHSコンパイラのelxrリンカにおけるリンカスクリプト(.ldファイル)の書き方がわからなかったため、ざっくり調べました。

概要

Linker Directiveファイル(通称リンカスクリプト)は、メモリ空間上にプログラムセクションをどのように配置するかをリンカに指示するためのファイルです。マイコンは「このメモリ番地にコードを割り当ててね」と指定されていることが多いため、組み込みソフト開発する上ではリンカスクリプトを書いておく必要があります。(一度書けば変更することはあまりないため、業務上では知識が属人化しやすい気がします)

リンカスクリプトはターゲットとするマイコンに依存するのは当然ですが、使用するリンカによっても記述が微妙に変わります。一般的によく使われるgccにはldというリンカがついていますが、今回はGHSコンパイラに付属するelxrリンカを想定します。ただし記述が共通する部分も多いため、より汎用化して理解するためにldと比較しながら書いていきます。

elxrリンカ

リンカスクリプトは基本的に、<コマンド>{ <指定内容> }といった文法で記述します。最低限必要なコマンドはSECTIONのみですが、非常に可読性が悪くなるためMEMORYコマンドも使われることが多いです。そのほかldと異なる点として、定数を定義するDEFAULTSコマンドなどがあります。ここではMEMORYSECTIONを取り上げます。

MEMORY

メモリ空間上の特定範囲のメモリ領域に名前をつけて定義します。フォーマットは以下です。

MEMORY{
    memory_block: ORIGIN=origin_expression, LENGTH=length_expression
}

MEMORY{}の中に定義したいメモリ領域を追加していきます。ここで定義したメモリ領域はSECTIONコマンド内で参照されます。

それぞれの要素は以下の意味です。

  • memory_block: メモリ領域の名前
  • origin_expression: メモリ領域の開始アドレス
  • length_expression: メモリ領域の長さ(バイト数)

例えば0x00000000番地〜0x00010000番地をROM_DATAと定義するとき、以下のように記述します。

ROM_DATA: ORIGIN=0x00000000, LENGTH=0x10000

LENGTHはK(1024)やM(10242)といった数値で指定することもできます。また数値を返す式で記述することも可能です。

// LENGTH=0x10000と等価
ROM_DATA: ORIGIN=0x00000000, LENGTH=64K

// 式による指定
ROM_DATA: ORIGIN=0x00000000, LENGTH=0x00010000-0x00000000

SECTION

リンクする各オブジェクトファイル内のプログラムセクション(.textなど)を、メモリ領域に割り当てます。プログラムセクションはCコード内やアセンブリ内で指定することもできますが、コンパイラによってデフォルトで割り付けられるものを使うことが多いです。主なデフォルトセクションは以下。(V850アーキテクチャ向けGHSコンパイラの例です)

セクション名 概要
.text プログラム・コード自体
.data 明示的に初期化された変数
.bss 明示的に初期化されない変数
.rodata constによる定数

test.cというファイルがコンパイルされると、ファイル内の情報が上記のセクションにそれぞれ配置され(便宜的にファイル単位セクションと呼びます)、test.oというオブジェクトファイルにまとめられます。SECTIONではこれらのオブジェクトファイル群を取り込み、ファイル単位セクションを統合して.text.dataといったプログラム全体のセクションに配置する指示を出します。

カスタムセクションを使用しない場合、最低限上記のセクションはメモリ領域のどこかに割り当てる必要があります。どこに割り当てるかはマイコンのデータシートとにらめっこですね。モノや動作設定によってはアクセスのスピードが異なる領域もあるので、要件を見直して決めることもあります。

(ldでは.stackというセクションの配置を指定する必要がありますが、GHSのランタイム環境は自動でこの辺の割り付けをしてくれるため特に設定する必要はありません。さすが有償ソフト)

フォーマットは以下のように指定します。

SECTION{
    secname [start_expression] [attribute] : [{contents}] [>memory_block | >.]
}
  • secname: 割り当てたいセクション名。.text.dataなど。自分で新たに設定することもできます。
  • start_expression: セクションの開始アドレス。memory_blockで指定する場合は不要。
  • attribute: セクションに付与したい属性。セクションの動作を設定できる。
  • contents: セクションの中身。配置したいファイル単位セクションをファイル名とセクション名で指定します。
  • memory_block: MEMORYで定義したメモリ領域に、secnameで指定したセクションを配置します。ロケーションカウンタを使用する場合は>.と記述することもできます。

[ ]はオプション記述です。secnameとstart_expression(またはmemory_block)さえ指定すれば配置できます。ファイル単位セクションの.dataを全てメモリ領域ROMに配置したいときは

.data : > ROM

のように記述できます。

contents指定

ファイル単位セクションと配置したいセクション名が異なる場合、contentsを指定します。全ファイルの.dataセクションを.mydataに配置し、メモリ領域ROMに配置したいときは以下のように書きます。

.mydata : {* (.data) } > ROM

ワイルドカード(*, ?)を使って、ファイル名・ファイル単位セクション名を指定することもできます。test_*.oにマッチするファイル内の.data_*セクションを.mydataに配置する場合は以下のようになります。特定の名付けルールがあるセクションを配置する際に便利です。

.mydata : { "test_*.o (.data_*)" }
関数の使用

リンカスクリプトがサポートする関数を使用すれば、アドレス指定などがやりやすくなります。elxrがサポートする主な関数は以下です。

  • ALIGN(n): nバイト境界までアラインされた現在の位置を返す。
  • ADDR(secname): セクションsecnameの開始アドレスを返す。
  • SIZEOF(secname): セクションsecnameの現在のサイズを返す。

次のセクションを4バイト境界に配置したい時は以下のように書きます。

.text : { . = ALIGN(4); *(.text) } > ROM
属性の使用

attributeを指定することで、セクションの動作を設定できます。おそらくldにはない機能の一つかと思います。属性はいろいろありますが、もはやマニュアル見た方が早いので割愛します。

(参考)ldリンカ

ldリンカのマニュアルは以下で確認できます。

Top (LD)

また、ldを使用したリンカスクリプトの記述は以下が参考になります。

blueeyes.sakura.ne.jp

MEMORYSECTIONの指定方法はほぼ同じです。elxrでは指定する必要がなかったOUTPUT_FORMATOUTPUT_ARCHを書く必要がありますが、マイコンアーキテクチャ固有の設定さえ覚えてしまえば、他プロダクトへの流用もできそうです。そこが一番めんどくさかったりするのですが…

まとめ

リンカスクリプトの書き方を調べました。ただ結局アーキテクチャやハード依存になってくる部分も多く、めちゃくちゃ泥臭い「製品固有の知識」になりがちなのが組み込みソフトのつらいところですね…もっとキラキラしたいんだよなぁ…

周回遅れで『SOFT SKILLS』を読んだ

今更ながら、少し前に話題になった「SOFT SKILLS」という本を読みました。もはや本を買うまでもないくらいネット上に書評等の記事が出回っていますが、記録のために書き残しておきます。

著者

John Z. Sonmezという方が執筆されています。この本自体が有名なのでググればすぐ出てきますが、33歳でアーリーリタイアするほど成功した人です。.NET系の開発者だったそうで、「SOFT SKILLS」執筆後のインタビュー記事も見つかりました。

type.jp

「Clean Architecture」などで有名なRobert C. Martinが序文を書いてるところも地味にすごいです。ソフトウェア開発者としてどれくらい優秀なのかは(少なくともこの本には書いていないので)わかりませんが、まさしく自分や自分の環境をハックするためのSOFT SKILLの鬼といったところでしょうか。

本の概要

この本に書かれていることは非常に多岐に渡ります。一言で言えば「自己啓発の欲張りセット」みたいな内容です。ソフトウェア開発者に向けて書いている部分もありますが、基本的にはすべての人に適応できる内容だと思います。この本を読んだ上で、特に気になる部分の関連書籍を読んで理解を深めるのも良いと思います。

ざっくりと書くと、全体的に以下のような構成となっています。

  • 第一部 キャリアを築こう
    • 「自己ブランディング」を常に意識した上で行動(対人折衝、キャリアパス構想)する
    • 雇われや起業等、働き方は多岐に渡るが、いずれの場合でも習慣化やモチベーション管理、難問への継続的な挑戦が大切である
  • 第二部 自分を売り込め!

    • キャリアの中でチャンスを掴むためには、積極的に自分をマーケティングする必要がある
    • 他人に届ける価値(分野・ターゲット・専門性など)を明確にすれば、より効果的にブランド化できる
    • (どのようなマーケティング手段を選んでも)批判や指摘を恐れずに、バカにされても気にしないマインドで小さく始めて継続する
  • 第三部 学ぶことを学ぼう

    • 優秀な開発者になるためには、独学の方法を学ぶことが必須である
    • 好奇心に従い、能動的に行動を起こしたとき(本書では"遊ぶとき"と表現)に最もよく学べ、他人に教えると理解が深まる
    • 学習する前にトピックのスコープを定め、成功の基準を明確にしておく
    • メンターを探す、または自分がメンターになると、自分の知識や理解の穴を見つけやすい
  • 第四部 生産性を高めよう
    • 集中状態に至るためには最初の5~10分を耐える
    • 四半期、月次、週次、日次の計画を立てる
    • ルーチン化することで、自分に責任を取らせるような内発的モチベーションを保ち、時間の浪費を防ぐ
    • 燃え尽き症候群に陥らないために、達成後に何があるかを常に意識する
    • 行動しなかった時に何が起こるかを考えながら、まず行動を起こす習慣をつける
  • 第五部 お金に強くなろう
    • 自分の資産がどれほどあり、負債への浪費にどれほど使っているかを把握するのが第一歩
    • 月々の出費を減らすことは、資産と負債の両面で効果的
  • 第六部 やっぱり、体が大事
    • 健康であることは単に寿命が長くなるというだけでなく、自身から成功を呼び込むことにつながる
    • 明確な目標を持ってとにかくやれ
    • モチベーション維持のための策をできるだけ講じる
  • 第七部 負けない心を鍛えよう
    • (非論理的であることは認めた上で)思考が現実に影響を与え、未来を変えることが多々ある
    • 状況はあくまで事実であり、捉え方次第でプラス思考になれる
    • 理想的な心理状態を思い出せる言葉やイメージを見つけ、一日の中で信じ続けることで思考を変える
    • 失敗を恐れず、成功したければ確実に失敗することが保証されているような状況に身を置く

要点

内容が多すぎるので、自分が参考になった部分のみ取り上げます。

アウトプットの重要性

本書ではしきりにブログや講演等によるアウトプットを推してきますが、主に以下の利点が挙げられています。

  • 自己ブランディングになる。同じ能力でアウトプットしていない人と比較すると、選ばれるチャンスが増える。
  • 学習のための深い理解につながる。知識を構造化して他人に伝えることで、自分の理解不足やさらなる問いが見つかる。
  • 資産になる。ブログや教材などが軌道に乗ればアフィリエイトによる受動収入源になりえる。

三つ目に関してはそれなりのクオリティを求められそうですが、基本的にメリットしかありません。アウトプットの中でもブログは最も手軽に始められる手段なので、ソフトウェア開発者なら全員やっておくべきといっても過言ではないのでしょう。「100人に自分のブログを持っているかを聞いたときに、継続して更新しているのは5,6人しかいない」と述べていたので、やるだけで既に上位10%に入れるわけです。(アウトプットしていない人の中にも優秀な人はいるはずですが、アウトプットしておけばより評価される機会が増えることは間違い無いでしょう)

成功基準を設けて学習する

何かを学習する手順として、本書では10ステップに分けた学習法を提案しています。その中でも最初の3ステップで

  • 学習対象の全体像を掴む
  • 学習するスコープを決める
  • 成功の基準を決める

ということをしています。多くの場合、その分野全体を理解するのは現実的に不可能であるため、「Linuxについて詳しくなる」のような曖昧な目標ではなく「Linuxのインストールとセットアップができ、コマンドを使用して特定の開発環境を構築できる」のように基準を明確に定義しておきます。これは成功体験が得られるだけでなく、目的と異なる周辺知識についてだらだら調査するような時間を削減できます(自分もよくやりがち)。

生産性向上のためのポモドーロテクニック

第四部の中で挙げられたテクニックです。これ自体は有名であり、他の書籍でもよく紹介されています。

25分集中して作業+5分休憩 という1セット(1ポモドーロと呼ぶ)でタスクを消化していくという単純なものですが、本書では特に「作業量の見積もり・トラッキング」で使用すると効果的であると述べています。

  1. あるタスクの作業をポモドーロテクニックで実施する
  2. 1日に何ポモドーロ実行できたかを計測する
  3. これを参考に1週間のタスクをポモドーロ単位で分割して見積もり、実行していく

これにより「仕事の進捗を実感し、十分仕事ができていないように感じることがなくなる」そうです。

実践

既に本の内容が実践レベルで書いてあるので蛇足感はありますが、これだけはやっていきたいところ。

継続的なアウトプット

これに関してはもはや言及するまでもないですが、継続するのは大変です。せっかくアウトプットしても半年に1回とかだと効果が薄そうですね…

せめて1週間に1回、月に4回程度は何らかのアウトプットが出せるようにしてみます。ブログだとよくネタに困る時があるので、

  • ネタにできそうなことを見つけたら逐一メモする
  • メモした内容について、ネット記事や書籍で知識を得る(可能であれば実践する)
  • 土日で記事を書く

というサイクルで実践していきたいです。継続に関しては「最初の10分を耐え切る」という心がけで習慣化していこうと思います。

ポモドーロ基準での見積もり

何らかのタスクの工数を見積もる時、今までは「類似タスクにどれくらい時間がかかったか」を参考にしていましたが、

  • いつも複数タスクを抱えているため、単一タスクの工数実績が記録にない
  • 類似と言っても全く同じことをするわけではないので、参考にならないことも多い

と言った理由で、見積もりから外れることがありました。そこでポモドーロを導入し、「タスクにかかる時間ではなく、単位時間(1ポモドーロ)で進められる作業量を把握し、タスクの大きさから逆算する」ことを実践しようと思います。

会社で働く以上は何らかの割り込みが発生しやすい(特に不具合調査とか)ため、本来のポモドーロテクニックがどれほど生かせるかは不明ですが、少しでも見積もり精度が上げたいので。

まとめ

当たり前ですが、こう言った自己啓発関連の助言は銀の弾丸にはなりえません。著者も様々な誘惑に耐えて今に至るそうなので、完璧な人間はいないしすぐに成果が出せる方法はないのでしょう。何事も気長に、楽しみながら継続してやるのが一番なのかもしれません。

昔は「自己啓発書は少しの間しかやる気がもたない、だからあまり読まない」と個人的に思っていましたが、逆に言えば「継続して読んでいれば半永久的にやる気を出せる」ような気がするので、また暇なときに読み返してみます。

VScodeでファイルの表示・検索・監視のExclude設定をする

ファイルの多いディレクトリをVScodeで開くと動作が重くなったり、grep検索の結果が見づらくなります。その対策として各種Exclude設定を調べたのでメモします。versionは以下を想定。

globパターンの記述

Exclude設定には、globと呼ばれるファイル名のパターンマッチングを表現するルールを使用します。globについては以下が詳しそうです。

www.atmarkit.co.jp

正規表現ほど柔軟ではないですが、ファイル名のパターンマッチングでそこまで複雑な操作をすることは稀なので、十分使える表現です。UNIXのglobでは主に以下の表現が使えます。

記号 概要 使用例(要求)
* 0文字以上の任意の文字列にマッチ 拡張子.txtのファイル *.txt
? 任意の1文字にマッチ 先頭が任意、その後"001"と続くファイル ?001
[ ] [ ]に含まれるいずれか1文字にマッチ "test0~test9"のファイル test[0-9]
! パターンの否定 A~Zで始まるファイル以外 [!A-Z]*
** 指定ディレクトリ以下を再起的に探索してマッチ "AA"ディレクトリ以下の.txtファイル AA/**/*.txt

Exclude設定

エクスプローラの表示ファイルから除外(Files: Exclude)

VScodeディレクトリを開いたときに、エクスプローラに表示されるファイルを除外することができます。ライブラリや設定ファイルを検索から除外するときに有効です。

Ctrl+,(macならCmd+,)で設定タブを開き、検索窓にfiles.excludeと入力すると該当の設定項目が開けます。

f:id:gco46:20201122142754p:plain

「パターンを追加」をクリックし、glob形式で除外したいファイルを記述します。setting.jsonに以下のように記述しても同様の設定が可能です。

{
    "search.exclude": {
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/CSV": true,
        "**/.DS_Store": true,
    }
}

検索結果から特定ファイルを除外(Search: Exclude)

VScodeでは開いたディレクトリ内でgrep検索が可能ですが、予め特定のファイルを検索対象から除外することができます。

設定タブから検索窓にsearch.excludeと入力し、設定します。

f:id:gco46:20201122142816p:plain

検索時に「除外するファイル」の右にあるスイッチをONにすると設定を反映した検索が可能です。(ここで検索毎に除外ファイルを指定することも可能です) f:id:gco46:20201122143634p:plain:w400

監視ファイルを除外(Files: Watcher Exclude)

VSCodeは使用中の動作を高速化するために、起動時にメモリを消費してディレクトリ内をある程度読み出してから起動します。サイズの大きいディレクトリを開こうとするとその分メモリを多く消費しますが、VSCodeが監視するファイルを除外するとこれを緩和できます。

設定タブから検索窓に'search.watcher'と入力し、設定します。設定後は再起動が必要です。

f:id:gco46:20201122142926p:plain

注意点

便利なexclude設定ですが、現状では例外設定がやりにくいです。例えばAAAディレクトリは検索から除外したいけど、AAAディレクトリ内のBBBだけは表示したい、といったときに

{
    "search.exclude": {
        "AAA/*": true,
        "!AAA/BBB": true,
    }
}

とか書くとできそうな気がしますが、環境によってできたりできなかったりするようです。これに関しては以下のissueで報告されています。

github.com

2015年にOpenして未だにCloseしていないのでMicrosoft的には優先度が低いバグなのかもしれません。我こそはという人は修正してpull requestしていただけると自分も助かります。。

まとめ

3つのExclude設定について述べました。VScodeはデフォルトでもそれなりに使い勝手の良いエディタですが、細かい設定をするとより扱い易くなります。進捗が出ないときなんかは息抜きに調べてみると良い気分転換になるのでおすすめです。

MongoDBのcollection基本操作をpythonで(pymongo)

pymongoによるMongoDBのcollectionに対する操作をしょっちゅう忘れるのでメモします。バージョンは以下を想定しています。基本的にはMongoDBでのshellコマンドがそのままpymongoで使用できます。

  • MongoDB == 4.4.1
  • pymongo == 3.10.1

検索

基本

検索時はfind()を使用し、以下のように条件を指定します。(MongoDBのshellでも同様です)

db.collection.find({条件})

pymongoでは、条件はdict形式で指定します。namehogeのドキュメントを検索するには、以下を条件に指定します。

{"name": "hoge"}

演算子の指定

mongoDBがサポートする演算子を使用すれば、複雑なクエリを実現できます。主な演算子は以下のとおりです。

演算子 type 概要 mongo shellでの例
$lt Comparison < {age: $lt: 20}}
$lte Comparison <= {age: {$lte: 20}}
$gt Comparison > {age: {$gt: 20}}
$gte Comparison >= {age: {$gte: 20}}
$ne Comparison != {age: {$ne: 20}}
$in Comparison 配列指定した値のいずれかにマッチする {tags: {$in: [, ]}}
$nin Comparison 配列指定した値のいずれにもマッチしない {tags: {$nin: [, ]}}
$and Logical 配列指定した条件のANDにマッチする {$and: [{cond1}, {cond2} ]}
$or Logical 配列指定した条件のORにマッチする {$or: [ {cond1}, {cond2} ]}
$exist Element 特定のフィールドを持つドキュメントにマッチする {name: {$exist:true}}
$regex Evaluation 指定した正規表現にマッチする {name: {$regex: /hoge/}}

クエリに関する他の演算子は以下の公式ドキュメントで確認できます。

docs.mongodb.com

上記の演算子をpymongoで使用する時は、演算子全体を文字列として指定します。例えば

AND ┳ age > 20
    ┣ languageに"English"または"Japanese"を含む
    ┣ TOEFL というフィールドを持つ
    ┗ name に Tanaka を含む

のような条件で検索したいときは、.find()の条件に以下を指定します。

# and条件のリスト
cond_l = [
  # age > 20
  {"age": {"$gt": 20} },
  # languageにEnglishかJapaneseを含む
  {"language": {"$in": ["English", "Japanese"]} },
  # TOEFL のフィールドを持つ
  {"TOEFL": {"$exist": True} },
  # nameにTanakaを含む('/'を使わない)
  {"name": {"$regex": "Tanaka"} }
]

# 条件のANDを取る
cond = {"$and": cond_l}

db.collection.find(cond)

注意点として、pymongoで正規表現を使用する際は/を使いません。pythonから操作する際は気を付けましょう。

更新

基本

shellでは以下が基本操作です。

db.collection.update({条件}, {更新演算子: {更新内容}})

更新演算子には多くの場合$setが使われ、「条件」に合致するドキュメントを「更新内容」で更新します。namehogeのドキュメントのage25に更新するとき、pymongoでは

update({"name": "hoge"}, {"$set": {"age": 25}}) 

のように記述します。更新するフィールドが複数ある場合は、「更新内容」に複数条件を並列して記述できます↓

{"$set": {"name": "Suzuki", "age": 25} }

更新演算子は必ず指定してください。以下のように書いてしまうと、該当するドキュメントを「更新内容」で上書きしてしまいます。

db.collection.update({条件}, {更新内容})

主な更新演算子は以下です。

演算子 type 概要 mongo shellでの例
$set Fields 指定したフィールドに値をセットする(なければ追加する) {$set: {age: 25}}
$unset Fields 指定したフィールドを削除する {$unset: {name: ""}}
$pop Array 指定したフィールドの配列の先頭または末尾を一つ削除する {$pop: {tags: -1}}
$push Array 指定したフィールドの配列に値を追加する {$push: {tags: "test"}}

更新演算子の一覧は以下のページで確認できます。Arrayの操作に関する演算子は使いこなせば色々なことができそうです。

docs.mongodb.com

複数ドキュメントの更新

shellによる操作では、update()ヒットした最初のドキュメントのみ更新します。これはpymongoでも同様です。複数更新するには、以下のようにoptionにmulti:trueを指定する必要があります。

db.collection.update({条件}, {$set: {更新内容}, {multi:true}}

pymongoでは以下のようにオプションを記述します。

db.collection.update({"field":"value"}, multi=True)

ただし、pymongoではupdate()は非推奨となっています。更新ドキュメント対象の意図を明確にするために、update_one()またはupdate_many()の使用を推奨されています。複数更新する場合はupdate_many()を使用しましょう。(mongoDBのshellにもupdateOne, updateManyがあるので、そちらを使うのが無難かもしれません)

Updateメソッドに関するMongoDBのドキュメントは以下で見ることができます。

docs.mongodb.com

削除

基本

以下のコマンドで、条件にマッチするドキュメントの削除ができます。

db.collection.remove({条件})

namehogeのドキュメントを削除する際のpymongoでのコマンドは以下になります。

db.collection.remove({"name": "hoge"})

複数ドキュメントの削除

remove()デフォルトで複数ドキュメントを削除するように動作します。条件にマッチした最初の一件のみ削除したい場合は、オプションとして{justOne: true}を指定できます。

db.collection.remove({条件}, {justOne:true})

pymongoではオプションの引数はmultiで指定します。

db.collection.remove({"field":"value"}, multi=False)

これも、削除したい件数の意図を明確にするためにdelete_one(), delete_many()が用意されています。(対応するmongo shellコマンドはdeleteOne(), deleteMany()です)特に理由がなければremoveは使わないようにしたほうが無難かもしれません。

その他使いそうなコマンド

ドキュメント追加

collectionにドキュメントを追加します。追加したいドキュメントはdict形式で指定します。

db.collection.insert({追加したいドキュメント})

件数取得

条件にマッチするドキュメントの件数を取得できます。

db.collection.find({条件}).count()

collection削除

指定したcollectionを削除します。

db.collection.drop()

collectionに対する他のshell コマンドは以下で確認できます。

docs.mongodb.com

pymongoのdocumentationも以下サイトで検索すると大体出てきます(めんどくさくなってきた)

pymongo.readthedocs.io

まとめ

主なmongo shellコマンドとpymongoでの使用法を調べました。MongoDBの公式ドキュメントは見やすくて助かります。NoSQLから入ってしまったのでいずれはRDBMSも学びたいところです。

参考

pymongoを使った様々な検索条件(AND/OR/部分一致/範囲検索) - Qiita

こんな時どうする? MongoDBクエリ逆引きリファレンス - Qiita

MongoDB超入門 - Qiita

巡回冗長検査(CRC, Cyclic Redundancy Check)の原理と実装

実務でCRC、巡回冗長検査を使うことがあったのですが、いまいちよくわかっていなかったので調べてみました。間違いあれば訂正します。

CRCとは

wikipediaの引用です。

巡回冗長検査(じゅんかいじょうちょうけんさ、英: Cyclic Redundancy Check, CRC)は、誤り検出符号の一種で、主にデータ転送などに伴う偶発的な誤りの検出によく使われている。送信側は定められた生成多項式で除算した余りを検査データとして付加して送信し、受信側で同じ生成多項式を使用してデータを除算し、その余りを比較照合することによって受信データの誤り・破損を検出する。

巡回冗長検査 - Wikipedia

生成多項式 G(x) を使用して送信するメッセージから検査データを作り、それを付加して送信します。受信側は受信したメッセージから検査データを作り、付加された検査データと比較して受信データの誤りを検出する仕組みです。

検査データには生成多項式による剰余を利用しています。厳密な数学(情報工学)の定義を抜きにすると、以下サイトの説明がわかりやすいです。CRCの根本的な考え方が書かれています。

http://funini.com/kei/math/crc_basic.shtml

使い始めるために

CRCを実装してください」と言われたら、少なくとも以下が決まれば実装して使い始めることができます。

  • 生成多項式
  • ビットシフト方向
  • 初期値
  • 出力(除算結果)のXOR反転パターン

生成多項式以外は、実装上の都合からくるものなので一旦おいておきます。CRCを(アイディア的な意味で)理解するためには最低限、生成多項式と二元系列での除算について知っておく必要があります。

生成多項式

生成多項式は、仕様上では以下のように指定されていたりします。

G(x) = x^ 4 + x^ 2 + x^ 1 + 1

CRCを扱う上では、二元系列の多項式表現という表現を使います。0, 1を成分とするベクトル {\boldsymbol{v} = (v_{n-1}, v_{n-2}, ... , v_{0} ) }は二元系列の多項式表現では以下のようになります。

 F(x) = v_ {n-1}x^ {n-1} + v_ {n-2}x^ {n-2} + ... + v_ 0

変数 xに大きな意味はありません。単に係数を区別するためのものです。CRCの計算では、この多項式の係数となる \boldsymbol{v}が使われます。先に上げた生成多項式は0, 1で表すと10111となります。(実際に使われる生成多項式はもっと長いですが、簡単のため短くしています)

二元系列での除算

難しいことは抜きにして説明すると、二元系列での除算はXORの繰り返しで実現されます。

例えば1001010110111で割る場合、以下のような計算になります。

f:id:gco46:20201108134701p:plain

剰余は11となりました。多項式で書くと、

 \displaystyle (x^7 + x^4 + x^2 + 1) = (x^3 + x)(x^4 + x^2 + x + 1) + (x + 1)

 (x^3 + x)が商、 (x+1)が剰余です。二元系列での演算はmod2で行われているため、違和感はありますがこれで正しいです。

CRCの計算

では実際に0x31B6というメッセージのCRCを計算してみます。これを二進数に直すと0011000110110110です。

計算は以下の手順で行われます。

  1. 生成多項式の次数だけ、メッセージの右側に0を加える
  2. ひたすらXORして剰余を求める

生成多項式 G(x) = x^{4} + x^2 + x + 1を使用します。次数は4なので、4つ0を右につけます。計算は以下です。

f:id:gco46:20201108134727p:plain

これにより、0x31B6CRC検査データは0110となります。検査データのビット数は生成多項式の次数と等しくなります。検査データを多項式表現すると (m-1)次式となるので、次数m多項式の剰余としては納得できる結果であると思います。

最初にメッセージの右側を0で埋めるのは、メッセージがそのままCRCとなるのを防ぐためです。CRCは任意のデータ長の検査データを作ることができますが、メッセージが (m-1)次だった場合、次数 mの生成多項式による剰余はそのままメッセージと等しくなってしまいます。(CRCが理論的に保証する性能を担保することができなくなります

実装

以上より、CRCのアイディアは説明されました。実装するにはまだ足りませんが、「なんかよくわからない検査ビットをくっつける」というレベルからは脱したと思います。

ここで、上記ケースCRCを計算するための実装例(python)を紹介します。 G(x) = x^4 + x^2 + x + 1を使った実装(便宜的にCRC4と名付けます)は以下です。

def make_crc4(message):
    # CRC計算結果(初期値は0000)
    crc = 0b0000
    # 生成多項式
    poly = 0b0111
    
    for i in range(len(message)):
        # データの逐次投入
        crc = crc ^ (message[i])
        for j in range(4):
            if (crc & 0b1000 == 0b1000):
                # 先頭ビットが1ならXOR
                # 左ビットシフト
                crc = poly ^ (crc << 1)
            else:
                crc = (crc << 1)

    # crcの下位4bitのみ使用(python特有の処理)
    return crc & 0b1111

# 0x31B6
# データの逐次投入のために、4bitずつに分割
data = [0b0011, 0b0001, 0b1011, 0b0110] 
crc4 = make_crc4(data)
print(bin(crc4))

(return crc & 0b1111は動的型付けであるpython特有の処理なので気にしなくて良いです。そもそもpython使うなって話ですが…)

例で挙げたCRCの計算は最もシンプルで、

  • 初期値==0
  • 左ビットシフト
  • 出力のXOR反転パターン==0000

での計算と等価です。実運用ではCRC計算を回路で実現することも多いので、ハードウェア実装の都合で仕様が決められたりします。規格として仕様が決められているものはググれば実装がすぐ出てくるので、CRCのアイディアだけ理解しておけばコピペで済みます。例えばCRC16-IBMのCによる実装は以下です。

http://www.soramimi.jp/crc16/

補足説明あれこれ

実装都合の知識

CRCのの根幹アイディアとググって出てくる実装例には乖離がありすぎるため、理解が難しいです。ここではその理解の助けになれるように知識を補完します。

生成多項式の先頭ビット省略

 G(x) = x^4 + x^2 + x + 110111となるはずですが、上の実装ではpoly = 0111となっています。除算をする場合、先頭の1はXORで必ず消えるため、「被除数の先頭ビットが1かどうかのみ確認し、1だったらG(x)(m-1)次以下の成分のXORを取る」という処理に置き換えられます。

f:id:gco46:20201108145241p:plain

「1ビットくらいいいじゃん」と思うかもしれませんが、実際のCRCは生成多項式の次数が32次とかにもなりえます。その1ビットのせいで4byteに収まらなくなるのは、リソース制約がある実務環境では嬉しくありません。

データの逐次投入

実装例で一番不思議に思うのが、crc = crc ^ message[i]の部分かと思います(自分はそうでした)。これはXOR演算に交換法則が成り立つことを利用した実装です。

CRCの根幹アイディアの通り愚直に実装しようとすると、(メッセージ+生成多項式の次数)ビットのデータを格納するための変数を用意する必要があります。実用上(イーサネットの規格など)では10000bitとかのメッセージに対してCRCを計算しますが、愚直実装では不可能なのが明らかです。そこでメッセージを小さい単位に分割して処理することを考えます。

CRCは結局の所、XOR演算の中で各桁に何回1が現れるかとカウントすれば良いので、厳密にはCRCの検査ビット数分だけの変数を用意しておけばよいです。このへんは言葉では説明しにくいですね…

f:id:gco46:20201108145255p:plain

この方法を利用すれば、計算に使用するRAM容量は一定のままで任意長のメッセージのCRCを計算できます。上の実装例ではCRC計算結果が4bitなので、4bitずつメッセージを読み込んでいます。実際には後で述べるテーブル参照を利用するために、1byte単位で読み込むことが多いです。

初期値の反転

データの逐次投入より、CRC結果を格納するための変数さえ用意すれば良いことがわかりました。実用上ではそのCRCの初期値を反転(全て1)させて計算する事が多いです。これはおそらくハードウェア実装に依存する問題です(調べましたが明確な理由は見つからなかったです…)。「全0のメッセージに対してのCRCが0になり、レジスタ値が全0になる故障が検出できなくなるのを防ぐため」という説が有力そうです。

XORの交換法則より、1が現れる回数が変わるだけなので通常時の演算は本質的には変わりません。

出力のXOR反転パターン

計算結果のCRCを決められたパターンのbit列とXORを取るだけです。0xFFFFとかが使われたりします。初期値の反転を実施した上で出力を全反転させると、結果的には反転が帳消しになります。これもおそらくハード実装の都合で決められる部分かと思います。

ビットシフト方向

演算時のビットシフトの方向を指定します。例では左シフトを挙げましたが、右シフトではビット順を反転させて計算します。すなわち、メッセージを

0011000110110110 (0x31B6)
 ↓
0110110110001100 (0x6D8C)

として右にビットシフトさせながら計算します。もちろん生成多項式も反転します。

ビットシフトは通信やハードの都合で決められます。LSBから伝送されるシリアル通信やプロセッサのエンディアンによって右ビットシフトが選択されるっぽいです。

テーブル参照

計算を高速化するためにテーブル参照という方法が取られることがあります。以下にCによるCRC16の実装を載せます。

unsigned short crc16(unsigned short crc, unsigned char const *ptr, int len)
{
#define CRC16POLY 0xa001
    int i, j;
    crc = ~crc;
    for (i = 0; i < len; i++) {
        crc ^= ptr[i];
        for (j = 0; j < 8; j++) {
            if (crc & 1) {
                crc = (crc >> 1) ^ CRC16POLY;
            } else {
                crc >>= 1;
            }
        }
    }
    return ~crc;
}

引用元:http://www.soramimi.jp/crc16/

python実装例でもそうでしたが、二重ループが含まれています(データ逐次投入のループ i と、ビットシフトのループ j )。逐次処理するデータの単位が1byteと決まっていれば、予め1byteで表現できる256通り分のCRC計算をしておいたテーブルを作成することで、ビットシフトのループをなくすことができます。詳細な実装例は上の引用元サイトで見ることができます。

当然ですが、テーブル参照方式ではROMの容量を消費するためリソースとは要相談です。2^8通り、つまり256通りx 2byte = 512byteくらいがテーブルの現実的なサイズになるので、自ずとデータ逐次投入の単位も1byteとなる事が多いようです。

CRCの特性

端的に言うと、CRCバースト誤りに対する強力な検出性能を持つことが理論的に保証されています。これが他の誤り検出(水平垂直パリティ検査符号やハミング符号)とは異なるCRCの特性です。バースト誤りとは、「長さnのメッセージ中で長さ l (\lt n ) の連続した誤りが一回起こること」です。これに対して、断続的に発生する誤りをランダム誤りと呼びます。

m次の生成多項式を使用した時、長さm区間内でのバースト誤りは全て検出可能です。また、生成多項式の選び方によりますがCRC-CCITTという規格の場合は(一定長以下のメッセージなら)3bitまでの任意のランダム誤りが検出できます。バースト誤りの証明は意外と単純なので興味があれば調べてみてください。以下の資料が参考になります。

http://www-ikn.ist.hokudai.ac.jp/~kida/lecture/IT_14.pdf

数学的な背景は以下サイトが参考になるかもしれません。

qiita.com

注意点として、生成多項式は適切なものを選ぶ必要があります。生成多項式は周期と呼ばれる数値を持っており、(メッセージ長 + CRC検査ビット長)が周期以下とならなければ誤り訂正能力が保証されません。例で挙げたCRC4の生成多項式の周期は7であるため、実際には誤り訂正できません(1bit誤り検出は可能)。大体は規格で決められているものを使うため問題になることはないと思います。ちなみにCRC-CCITTの周期は32767だそうです。

まとめ

CRCのアイディアと実装について述べました。なんとか使える程度には理解できたと思います。

余談ですが、CRCはあくまで誤り検出のための手法であるため悪意のある攻撃に対しては無力です。

qiita.com

使い所はしっかり理解した上で利用したいですね。

参考

https://taekwongineer.hatenablog.jp/entry/2020/03/25/233948

https://www.slideshare.net/7shi/crc32

プロセス改善の第一歩に『ハイブリッドアジャイルの実践』

アジャイルというプロセスは、主に小規模な開発案件で適応できるものかと思っていましたが、以下の本を見つけたので読んでみました。

大規模案件へのアジャイル適応のノウハウを事例とともに紹介したものです。ベーシックなアジャイルの導入が難しいときに、部分的にアジャイルの考え方を取り込んだ「ハイブリッドアジャイル」を提唱しています。

著者

株式会社日立ソリューションズの方々が執筆しています。日立ソリューションズは日立グループのIT分野の中枢企業であり、大規模なシステム開発案件も手掛けています。グループ内には別のIT系子会社もあるのですが、日立ソリューションズはその中でも設計に強みを持っているらしいです(中の人談)。本書では、関西電力向けの大規模システムにアジャイルを適用したときの事例が取り上げられています。

本の概要

開発モデルの選択

アジャイルといえばスクラムやXP等の方法論が語られる事が多いですが、それらは数ある方法論の一つでしかありません。アジャイルマニフェストによって宣言されているもののうち、プロダクトが何を重要視するかによって採用すべき開発モデルは変わります。(方法論を愚直に実行するだけでは、開発規模の大小に関わらず成功しません)

大規模開発のウォーターフォール(WF)型をベースとする場合、プロセス全てにアジャイルを導入するのは困難なので、改善すべき課題に合わせて部分的にアジャイル開発を適用します。たとえば

などがあります。

開発プロセスの作り方

開発モデルの選択でも述べましたが、開発プロセスを作る上では目的(現プロセスでの課題)を明確にしてから解決策を検討する必要があります。目的は例えば

  • イムリーな市場投入
  • 優先的変更への対応
  • 生産性向上

などがあります。ここで決めた目的と、プロジェクトの特性に応じてプロセスを決定します。

ここから、以下の項目について検討します。

  • 作業内容の改善
    • どの作業を削減するか
    • どうやって作業スピードを上げるか
  • プロジェクトの管理方法・体制の検討
    • 進捗管理の方法
    • コスト管理(システム規模の見積もり)方法
    • 品質基準
    • 変更管理(変更を受け入れるタイミングと手順)

これらを事前決定して初めてプロセスが適用できます。プロセスを実施し、イテレーション毎に改善を進めながら開発を進めることが基本です。イテレーション後・開発完了後の節目には定量評価、定性評価の実施も忘れず行います。

プロジェクト計画

主に以下に留意しながら、プロジェクトを進めます。

  • 破綻を避けるための方策
    • 仕様変更への対応期限を確定させた上で、ゆとりをもたせたスケジュールを組む
    • コスト上限を考慮して計画・委託する
    • イテレーション毎に変更管理を実施し、対応要否を検討する
  • 適用プラクティスの検討

要点

  • プロセス定義の時点で、必ず作業量の削減、作業スピードの向上策を決める(WFモデルと同様の作業としない)
    • どの時点でどこまでドキュメントが必要かを明確にし、喫緊でないドキュメントは後回し or 簡略化する。
    • 仕様の伝達(不明点の確認)はコミュニケーションベースで。そのために朝会等の双方向伝達を定期的に実施する。
    • 伝達ロス削減のため同アイテムに関しては同じ人が設計〜テストまで実施する。
  • アジャイル固有の作業を必ず計画する
    • イテレーション毎のレトロスペクティブとか
    • 変更要望の受け入れ検討時間とかタイミングとか
  • 進捗管理はWFよりも細かい単位で行う
    • イテレーション単位と日次単位で
    • 一日以下で完了できる作業を最小単位とし、進捗の傾向をすぐに把握できるようにする

実践

ペーペーの見習いなので実際にアジャイルを主導するのはだいぶ先ですが、実践する上で以下のプラクティスは優先的に取り入れようと思います。

削減する作業を明確にする

要点でも述べたように、プロセスの実施順を変えただけではアジャイルの成果は見込めません。おそらく多くの場合で、詳細設計等の「品質への影響が小さいドキュメントの作成」を省くことになるでしょう。プロジェクトによって全く作らないか後追いで作るかはまちまちですが、品質要求が高いほど最終的に求められる成果物の量が多くなると思います。「いつの時点で」「どこまで作るか」を予め明確にしておく(必要であれば顧客と整合しておく)ことで、開発者がいちいちドキュメントの粒度や分量を気にせず作業できると思いました。むしろプロセス定義とかそういった次元の話かもしれません。(そういう働きかけをPMまたはPLが積極的に行う事が前提となりますが、少なくとも弊チームでは見たことがありません…)

コミュニケーションを絶やさない

当たり前のことですが大切です。朝会で昨日やったこと、今日やること、懸念点、気がついたことを共有します。そのためにはタスクを日次レベルまで落とし込む必要があり(そうしないと毎日「〇〇の検討をします」みたいな中身のない報告会になります)、進捗管理もやりやすくなります。そもそもアイテム単位の計画を開発メンバが各々で立てて報告するのが最善なのですが、これが以外と難しい。

加えてレトロスペクティブを欠かさないことです。誰がどういった機能を担当したか、どういった問題が発生したか等は最低限共有しないと、自分が次に担当する機能への影響が把握できません(仕様や課題解決の伝達がドキュメントベースだと時間がかかる)。組み込み系ならハードチーム、ソフトチームで速やかに情報共有できる仕組みがほしいですね。

他社の実例に基づく

やっぱり偉い人は前例を欲しがるので、なんやかんやこれに落ち着くかもしれないです。規模の大きい開発の中でも、車載ECUのソフト開発という品質要求の高い事例でのアジャイル適用事例を見つけました。

https://www.juse.jp/sqip/symposium/2019/timetable/files/C2-1_happyou.pdf

ここでは本書で紹介されたプラクティスがいくつか使われていますが、車載開発用にテーラリングされ実績を残しています。個人的には「各メンバの担当、委託先とプロパの責任範囲を明確にする」ということが開発開始前にしっかり行われているのがすごいと思いました。これも当たり前の話なんですが、どうも自分の現場はそこらへんがテキトーでまかり通っているので…。

まとめ

WFモデルをベースに、アジャイルの要素を取り入れるための考え方を学びました。やはり実践にはアジャイルに精通した人が最低一人は必要で、かつプロセス再定義に積極的なPMがいることがスタートラインとなりそうです。