tagsファイルをパースして関数一覧を取得する
プロジェクト内の関数一覧を取得したいとPLに言われたので、適当に方法を考えてみました。構文解析から愚直にやると地獄を見そうなので、今回はCtagsを利用し、tagsファイルをパースするスクリプトをpythonで書きます。
結論
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)はソース及びヘッダ内にある名前のインデックス(又はタグ)ファイルを生成するプログラム。様々なプログラミング言語に対応している。言語に依存するが、サブルーチン(関数)、変数、クラスのメンバ、マクロ等がインデックス化される。これらのタグによりテキストエディタなどのツールで高速かつ容易に定義を参照できる。相互参照ファイルを出力でき、また名前についての情報を人が読みやすい形で列挙した言語ファイルを生成することもできる。
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=<言語の名前>
$ 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
コマンドなどがあります。ここではMEMORY
とSECTION
を取り上げます。
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
リンカのマニュアルは以下で確認できます。
また、ld
を使用したリンカスクリプトの記述は以下が参考になります。
MEMORY
やSECTION
の指定方法はほぼ同じです。elxr
では指定する必要がなかったOUTPUT_FORMAT
やOUTPUT_ARCH
を書く必要がありますが、マイコンやアーキテクチャ固有の設定さえ覚えてしまえば、他プロダクトへの流用もできそうです。そこが一番めんどくさかったりするのですが…
まとめ
リンカスクリプトの書き方を調べました。ただ結局アーキテクチャやハード依存になってくる部分も多く、めちゃくちゃ泥臭い「製品固有の知識」になりがちなのが組み込みソフトのつらいところですね…もっとキラキラしたいんだよなぁ…
周回遅れで『SOFT SKILLS』を読んだ
今更ながら、少し前に話題になった「SOFT SKILLS」という本を読みました。もはや本を買うまでもないくらいネット上に書評等の記事が出回っていますが、記録のために書き残しておきます。
著者
John Z. Sonmezという方が執筆されています。この本自体が有名なのでググればすぐ出てきますが、33歳でアーリーリタイアするほど成功した人です。.NET系の開発者だったそうで、「SOFT SKILLS」執筆後のインタビュー記事も見つかりました。
「Clean Architecture」などで有名なRobert C. Martinが序文を書いてるところも地味にすごいです。ソフトウェア開発者としてどれくらい優秀なのかは(少なくともこの本には書いていないので)わかりませんが、まさしく自分や自分の環境をハックするためのSOFT SKILLの鬼といったところでしょうか。
本の概要
この本に書かれていることは非常に多岐に渡ります。一言で言えば「自己啓発の欲張りセット」みたいな内容です。ソフトウェア開発者に向けて書いている部分もありますが、基本的にはすべての人に適応できる内容だと思います。この本を読んだ上で、特に気になる部分の関連書籍を読んで理解を深めるのも良いと思います。
ざっくりと書くと、全体的に以下のような構成となっています。
- 第一部 キャリアを築こう
第二部 自分を売り込め!
第三部 学ぶことを学ぼう
- 優秀な開発者になるためには、独学の方法を学ぶことが必須である
- 好奇心に従い、能動的に行動を起こしたとき(本書では"遊ぶとき"と表現)に最もよく学べ、他人に教えると理解が深まる
- 学習する前にトピックのスコープを定め、成功の基準を明確にしておく
- メンターを探す、または自分がメンターになると、自分の知識や理解の穴を見つけやすい
- 第四部 生産性を高めよう
- 集中状態に至るためには最初の5~10分を耐える
- 四半期、月次、週次、日次の計画を立てる
- ルーチン化することで、自分に責任を取らせるような内発的モチベーションを保ち、時間の浪費を防ぐ
- 燃え尽き症候群に陥らないために、達成後に何があるかを常に意識する
- 行動しなかった時に何が起こるかを考えながら、まず行動を起こす習慣をつける
- 第五部 お金に強くなろう
- 自分の資産がどれほどあり、負債への浪費にどれほど使っているかを把握するのが第一歩
- 月々の出費を減らすことは、資産と負債の両面で効果的
- 第六部 やっぱり、体が大事
- 健康であることは単に寿命が長くなるというだけでなく、自身から成功を呼び込むことにつながる
- 明確な目標を持ってとにかくやれ
- モチベーション維持のための策をできるだけ講じる
- 第七部 負けない心を鍛えよう
- (非論理的であることは認めた上で)思考が現実に影響を与え、未来を変えることが多々ある
- 状況はあくまで事実であり、捉え方次第でプラス思考になれる
- 理想的な心理状態を思い出せる言葉やイメージを見つけ、一日の中で信じ続けることで思考を変える
- 失敗を恐れず、成功したければ確実に失敗することが保証されているような状況に身を置く
要点
内容が多すぎるので、自分が参考になった部分のみ取り上げます。
アウトプットの重要性
本書ではしきりにブログや講演等によるアウトプットを推してきますが、主に以下の利点が挙げられています。
- 自己ブランディングになる。同じ能力でアウトプットしていない人と比較すると、選ばれるチャンスが増える。
- 学習のための深い理解につながる。知識を構造化して他人に伝えることで、自分の理解不足やさらなる問いが見つかる。
- 資産になる。ブログや教材などが軌道に乗ればアフィリエイトによる受動収入源になりえる。
三つ目に関してはそれなりのクオリティを求められそうですが、基本的にメリットしかありません。アウトプットの中でもブログは最も手軽に始められる手段なので、ソフトウェア開発者なら全員やっておくべきといっても過言ではないのでしょう。「100人に自分のブログを持っているかを聞いたときに、継続して更新しているのは5,6人しかいない」と述べていたので、やるだけで既に上位10%に入れるわけです。(アウトプットしていない人の中にも優秀な人はいるはずですが、アウトプットしておけばより評価される機会が増えることは間違い無いでしょう)
成功基準を設けて学習する
何かを学習する手順として、本書では10ステップに分けた学習法を提案しています。その中でも最初の3ステップで
- 学習対象の全体像を掴む
- 学習するスコープを決める
- 成功の基準を決める
ということをしています。多くの場合、その分野全体を理解するのは現実的に不可能であるため、「Linuxについて詳しくなる」のような曖昧な目標ではなく「Linuxのインストールとセットアップができ、コマンドを使用して特定の開発環境を構築できる」のように基準を明確に定義しておきます。これは成功体験が得られるだけでなく、目的と異なる周辺知識についてだらだら調査するような時間を削減できます(自分もよくやりがち)。
生産性向上のためのポモドーロテクニック
第四部の中で挙げられたテクニックです。これ自体は有名であり、他の書籍でもよく紹介されています。
25分集中して作業+5分休憩 という1セット(1ポモドーロと呼ぶ)でタスクを消化していくという単純なものですが、本書では特に「作業量の見積もり・トラッキング」で使用すると効果的であると述べています。
- あるタスクの作業をポモドーロテクニックで実施する
- 1日に何ポモドーロ実行できたかを計測する
- これを参考に1週間のタスクをポモドーロ単位で分割して見積もり、実行していく
これにより「仕事の進捗を実感し、十分仕事ができていないように感じることがなくなる」そうです。
実践
既に本の内容が実践レベルで書いてあるので蛇足感はありますが、これだけはやっていきたいところ。
継続的なアウトプット
これに関してはもはや言及するまでもないですが、継続するのは大変です。せっかくアウトプットしても半年に1回とかだと効果が薄そうですね…
せめて1週間に1回、月に4回程度は何らかのアウトプットが出せるようにしてみます。ブログだとよくネタに困る時があるので、
- ネタにできそうなことを見つけたら逐一メモする
- メモした内容について、ネット記事や書籍で知識を得る(可能であれば実践する)
- 土日で記事を書く
というサイクルで実践していきたいです。継続に関しては「最初の10分を耐え切る」という心がけで習慣化していこうと思います。
ポモドーロ基準での見積もり
何らかのタスクの工数を見積もる時、今までは「類似タスクにどれくらい時間がかかったか」を参考にしていましたが、
- いつも複数タスクを抱えているため、単一タスクの工数実績が記録にない
- 類似と言っても全く同じことをするわけではないので、参考にならないことも多い
と言った理由で、見積もりから外れることがありました。そこでポモドーロを導入し、「タスクにかかる時間ではなく、単位時間(1ポモドーロ)で進められる作業量を把握し、タスクの大きさから逆算する」ことを実践しようと思います。
会社で働く以上は何らかの割り込みが発生しやすい(特に不具合調査とか)ため、本来のポモドーロテクニックがどれほど生かせるかは不明ですが、少しでも見積もり精度が上げたいので。
まとめ
当たり前ですが、こう言った自己啓発関連の助言は銀の弾丸にはなりえません。著者も様々な誘惑に耐えて今に至るそうなので、完璧な人間はいないしすぐに成果が出せる方法はないのでしょう。何事も気長に、楽しみながら継続してやるのが一番なのかもしれません。
昔は「自己啓発書は少しの間しかやる気がもたない、だからあまり読まない」と個人的に思っていましたが、逆に言えば「継続して読んでいれば半永久的にやる気を出せる」ような気がするので、また暇なときに読み返してみます。
VScodeでファイルの表示・検索・監視のExclude設定をする
ファイルの多いディレクトリをVScodeで開くと動作が重くなったり、grep検索の結果が見づらくなります。その対策として各種Exclude設定を調べたのでメモします。versionは以下を想定。
- vscode == 1.51.1
globパターンの記述
Exclude設定には、globと呼ばれるファイル名のパターンマッチングを表現するルールを使用します。globについては以下が詳しそうです。
正規表現ほど柔軟ではないですが、ファイル名のパターンマッチングでそこまで複雑な操作をすることは稀なので、十分使える表現です。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
と入力すると該当の設定項目が開けます。
「パターンを追加」をクリックし、glob形式で除外したいファイルを記述します。setting.json
に以下のように記述しても同様の設定が可能です。
{ "search.exclude": { "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CSV": true, "**/.DS_Store": true, } }
検索結果から特定ファイルを除外(Search: Exclude)
VScodeでは開いたディレクトリ内でgrep検索が可能ですが、予め特定のファイルを検索対象から除外することができます。
設定タブから検索窓にsearch.exclude
と入力し、設定します。
検索時に「除外するファイル」の右にあるスイッチをONにすると設定を反映した検索が可能です。(ここで検索毎に除外ファイルを指定することも可能です)
監視ファイルを除外(Files: Watcher Exclude)
VSCodeは使用中の動作を高速化するために、起動時にメモリを消費してディレクトリ内をある程度読み出してから起動します。サイズの大きいディレクトリを開こうとするとその分メモリを多く消費しますが、VSCodeが監視するファイルを除外するとこれを緩和できます。
設定タブから検索窓に'search.watcher'と入力し、設定します。設定後は再起動が必要です。
注意点
便利なexclude設定ですが、現状では例外設定がやりにくいです。例えばAAA
ディレクトリは検索から除外したいけど、AAA
ディレクトリ内のBBB
だけは表示したい、といったときに
{ "search.exclude": { "AAA/*": true, "!AAA/BBB": true, } }
とか書くとできそうな気がしますが、環境によってできたりできなかったりするようです。これに関しては以下のissueで報告されています。
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形式で指定します。name
がhoge
のドキュメントを検索するには、以下を条件に指定します。
{"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/}} |
クエリに関する他の演算子は以下の公式ドキュメントで確認できます。
上記の演算子を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
が使われ、「条件」に合致するドキュメントを「更新内容」で更新します。name
がhoge
のドキュメントのage
を25
に更新するとき、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の操作に関する演算子は使いこなせば色々なことができそうです。
複数ドキュメントの更新
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のドキュメントは以下で見ることができます。
削除
基本
以下のコマンドで、条件にマッチするドキュメントの削除ができます。
db.collection.remove({条件})
name
がhoge
のドキュメントを削除する際の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 コマンドは以下で確認できます。
pymongoのdocumentationも以下サイトで検索すると大体出てきます(めんどくさくなってきた)
まとめ
主なmongo shellコマンドとpymongoでの使用法を調べました。MongoDBの公式ドキュメントは見やすくて助かります。NoSQLから入ってしまったのでいずれはRDBMSも学びたいところです。
参考
pymongoを使った様々な検索条件(AND/OR/部分一致/範囲検索) - Qiita
巡回冗長検査(CRC, Cyclic Redundancy Check)の原理と実装
実務でCRC、巡回冗長検査を使うことがあったのですが、いまいちよくわかっていなかったので調べてみました。間違いあれば訂正します。
CRCとは
wikipediaの引用です。
巡回冗長検査(じゅんかいじょうちょうけんさ、英: Cyclic Redundancy Check, CRC)は、誤り検出符号の一種で、主にデータ転送などに伴う偶発的な誤りの検出によく使われている。送信側は定められた生成多項式で除算した余りを検査データとして付加して送信し、受信側で同じ生成多項式を使用してデータを除算し、その余りを比較照合することによって受信データの誤り・破損を検出する。
生成多項式 を使用して送信するメッセージから検査データを作り、それを付加して送信します。受信側は受信したメッセージから検査データを作り、付加された検査データと比較して受信データの誤りを検出する仕組みです。
検査データには生成多項式による剰余を利用しています。厳密な数学(情報工学)の定義を抜きにすると、以下サイトの説明がわかりやすいです。CRCの根本的な考え方が書かれています。
http://funini.com/kei/math/crc_basic.shtml
使い始めるために
「CRCを実装してください」と言われたら、少なくとも以下が決まれば実装して使い始めることができます。
- 生成多項式
- ビットシフト方向
- 初期値
- 出力(除算結果)のXOR反転パターン
生成多項式以外は、実装上の都合からくるものなので一旦おいておきます。CRCを(アイディア的な意味で)理解するためには最低限、生成多項式と二元系列での除算について知っておく必要があります。
生成多項式
生成多項式は、仕様上では以下のように指定されていたりします。
CRCを扱う上では、二元系列の多項式表現という表現を使います。0, 1を成分とするベクトルは二元系列の多項式表現では以下のようになります。
変数に大きな意味はありません。単に係数を区別するためのものです。CRCの計算では、この多項式の係数となるが使われます。先に上げた生成多項式は0, 1で表すと10111
となります。(実際に使われる生成多項式はもっと長いですが、簡単のため短くしています)
二元系列での除算
難しいことは抜きにして説明すると、二元系列での除算はXORの繰り返しで実現されます。
例えば10010101
を10111
で割る場合、以下のような計算になります。
剰余は11
となりました。多項式で書くと、
が商、が剰余です。二元系列での演算はmod2で行われているため、違和感はありますがこれで正しいです。
CRCの計算
では実際に0x31B6
というメッセージのCRCを計算してみます。これを二進数に直すと0011000110110110
です。
計算は以下の手順で行われます。
- 生成多項式の次数だけ、メッセージの右側に
0
を加える - ひたすらXORして剰余を求める
生成多項式はを使用します。次数は4なので、4つ0を右につけます。計算は以下です。
これにより、0x31B6
のCRC検査データは0110
となります。検査データのビット数は生成多項式の次数と等しくなります。検査データを多項式表現すると次式となるので、次数の多項式の剰余としては納得できる結果であると思います。
最初にメッセージの右側を0で埋めるのは、メッセージがそのままCRCとなるのを防ぐためです。CRCは任意のデータ長の検査データを作ることができますが、メッセージが次だった場合、次数の生成多項式による剰余はそのままメッセージと等しくなってしまいます。(CRCが理論的に保証する性能を担保することができなくなります)
実装
以上より、CRCのアイディアは説明されました。実装するにはまだ足りませんが、「なんかよくわからない検査ビットをくっつける」というレベルからは脱したと思います。
ここで、上記ケースCRCを計算するための実装例(python)を紹介します。を使った実装(便宜的に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による実装は以下です。
補足説明あれこれ
実装都合の知識
CRCのの根幹アイディアとググって出てくる実装例には乖離がありすぎるため、理解が難しいです。ここではその理解の助けになれるように知識を補完します。
生成多項式の先頭ビット省略
は10111
となるはずですが、上の実装ではpoly = 0111
となっています。除算をする場合、先頭の1はXORで必ず消えるため、「被除数の先頭ビットが1かどうかのみ確認し、1だったらの次以下の成分のXORを取る」という処理に置き換えられます。
「1ビットくらいいいじゃん」と思うかもしれませんが、実際のCRCは生成多項式の次数が32次とかにもなりえます。その1ビットのせいで4byteに収まらなくなるのは、リソース制約がある実務環境では嬉しくありません。
データの逐次投入
実装例で一番不思議に思うのが、crc = crc ^ message[i]
の部分かと思います(自分はそうでした)。これはXOR演算に交換法則が成り立つことを利用した実装です。
CRCの根幹アイディアの通り愚直に実装しようとすると、(メッセージ+生成多項式の次数)ビットのデータを格納するための変数を用意する必要があります。実用上(イーサネットの規格など)では10000bitとかのメッセージに対してCRCを計算しますが、愚直実装では不可能なのが明らかです。そこでメッセージを小さい単位に分割して処理することを考えます。
CRCは結局の所、XOR演算の中で各桁に何回1が現れるかとカウントすれば良いので、厳密にはCRCの検査ビット数分だけの変数を用意しておけばよいです。このへんは言葉では説明しにくいですね…
この方法を利用すれば、計算に使用する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の容量を消費するためリソースとは要相談です。通り、つまり256通りx 2byte = 512byteくらいがテーブルの現実的なサイズになるので、自ずとデータ逐次投入の単位も1byteとなる事が多いようです。
CRCの特性
端的に言うと、CRCはバースト誤りに対する強力な検出性能を持つことが理論的に保証されています。これが他の誤り検出(水平垂直パリティ検査符号やハミング符号)とは異なるCRCの特性です。バースト誤りとは、「長さのメッセージ中で長さの連続した誤りが一回起こること」です。これに対して、断続的に発生する誤りをランダム誤りと呼びます。
次の生成多項式を使用した時、長さの区間内でのバースト誤りは全て検出可能です。また、生成多項式の選び方によりますがCRC-CCITTという規格の場合は(一定長以下のメッセージなら)3bitまでの任意のランダム誤りが検出できます。バースト誤りの証明は意外と単純なので興味があれば調べてみてください。以下の資料が参考になります。
http://www-ikn.ist.hokudai.ac.jp/~kida/lecture/IT_14.pdf
数学的な背景は以下サイトが参考になるかもしれません。
注意点として、生成多項式は適切なものを選ぶ必要があります。生成多項式は周期と呼ばれる数値を持っており、(メッセージ長 + CRC検査ビット長)が周期以下とならなければ誤り訂正能力が保証されません。例で挙げたCRC4の生成多項式の周期は7であるため、実際には誤り訂正できません(1bit誤り検出は可能)。大体は規格で決められているものを使うため問題になることはないと思います。ちなみにCRC-CCITTの周期は32767だそうです。
まとめ
CRCのアイディアと実装について述べました。なんとか使える程度には理解できたと思います。
余談ですが、CRCはあくまで誤り検出のための手法であるため悪意のある攻撃に対しては無力です。
使い所はしっかり理解した上で利用したいですね。
参考
プロセス改善の第一歩に『ハイブリッドアジャイルの実践』
アジャイルというプロセスは、主に小規模な開発案件で適応できるものかと思っていましたが、以下の本を見つけたので読んでみました。
大規模案件へのアジャイル適応のノウハウを事例とともに紹介したものです。ベーシックなアジャイルの導入が難しいときに、部分的にアジャイルの考え方を取り込んだ「ハイブリッドアジャイル」を提唱しています。
著者
株式会社日立ソリューションズの方々が執筆しています。日立ソリューションズは日立グループの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がいることがスタートラインとなりそうです。