Posts for: #Programming

excel が utf-8 で保存する csv ファイルは bom 付き

昨日の定例会議で csv ファイルのエンコーディングの話しになって excel 2016 以降では utf-8 で csv ファイルを保存できるようになった。もう cp932 対応しなくてよいといった話題が出た。そのときにうろ覚えだったものの、excel は byte order mark (bom) を付けていた気がして、実際にファイルを作ってもらったらそうだった。bom の有無は file コマンドや od コマンドでなどで調べられる。

$ file book1.csv
book1.csv: Unicode text, UTF-8 (with BOM) text, with CRLF line terminators

ファイルの先頭に 0xef 0xbb 0xbf の3バイトがつく。

$ head -1 | od -t x1 book1.csv
0000000    ef  bb  bf  68  65  61  64  65  72  31  2c  68  65  61  64  65
0000020    72  32  2c  68  65  61  64  65  72  33  2c  68  65  61  64  65
...

印字可能な文字ではないため、テキストにするとわからないが、byte 列だと bom が付いているのがわかる。16進数の 0x68 が ‘h’ になる。bom がついていると header1 という文字列比較したときに別の文字列になってしまうので取り除かないといけない。

文字列: 'header1'
byte列: 'efbbbf68656164657231'

いろんな対応方法があると思うが、so の回答 でみつけたこの方法がコードの見通しもよくて気に入った。次のように io.Reader をラップするようなコードになる。

func newBOMAwaredCSVReader(reader io.Reader) *csv.Reader {
	transformer := unicode.BOMOverride(encoding.Nop.NewDecoder())
	return csv.NewReader(transform.NewReader(reader, transformer))
}

Transformer という仕組みがあって、準標準ライブラリの golang.org/x/text/encoding で実装されている。so の回答をみただけでコードを追加するのもよくないかと思って unicode#BOMOverride が返す bomOverride transformer のコードを読んで把握した上でテストを書いてマージリクエストを送った。実際の変換処理は次のようなもの。

  • fallback として encoding.Nop を使う
  • 最初に呼ばれたときに d.current (fallback) をセットして、2回目以降は fallback が呼ばれる
  • 与えられた byte 列が 2byte 以上のときに bom のチェックを行う
  • bom があるときはそのバイト数を読み飛ばして d.current (fallback) で変換する
func (d *bomOverride) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
	if d.current != nil {
		return d.current.Transform(dst, src, atEOF)
	}
	if len(src) < 3 && !atEOF {
		return 0, 0, transform.ErrShortSrc
	}
	d.current = d.fallback
	bomSize := 0
	if len(src) >= 2 {
		if src[0] == 0xFF && src[1] == 0xFE {
			d.current = utf16le.NewDecoder()
			bomSize = 2
		} else if src[0] == 0xFE && src[1] == 0xFF {
			d.current = utf16be.NewDecoder()
			bomSize = 2
		} else if len(src) >= 3 &&
			src[0] == utf8BOM[0] &&
			src[1] == utf8BOM[1] &&
			src[2] == utf8BOM[2] {
			d.current = transform.Nop
			bomSize = 3
		}
	}
	if bomSize < len(src) {
		nDst, nSrc, err = d.current.Transform(dst, src[bomSize:], atEOF)
	}
	return nDst, nSrc + bomSize, err
}

自分で実装してもなにも難しくないけど、すでに実績のあるコードがあるならそれを再利用した方が保守コストを削減できる。

同期飲み会

新卒入社した会社の同期との飲み会。2ヶ月前と同じメンバー で飲んできた。4人で飲む予定が、また1人が障害対応でドタキャンになったから3人で飲んでた。日本酒原価酒蔵 という、おいしい日本酒を原価で提供するお店があるみたい。プレミアム飲み放題プランだったのでよいお酒をいろいろ飲み比べできた。どれもおいしかったし、ちょっとずついろいろ飲み比べできておもしろかった。飲み放題メニューに神戸のお酒がなかったのがよいことなのか残念なのか。前半は辛口の日本酒を飲んで、だんだん酔っ払って味がわからなくなるから、後半にコクや甘みの強い日本酒を飲むとおいしく飲めると教えてもらった。本当にその通りで日本酒の飲み方のよい勉強になった。お店で飲んだお酒のカードがもらえる。酔っ払って忘れてしまっても後で思い出せる。

特徴的なお酒で記憶に残っているもので 三井の寿 がスラムダンクに出てくる三井寿に由来して命名されていて、その背番号である14番と同じアルコード度数14度、日本酒度 (辛口甘口の度合いを表す) +14 と意図的?にあっているとのこと。日本酒の中ではトップクラスの辛口らしい。たしかに過去に私が飲んだことがないキレだった。もう1つは 讃岐くらうでぃ という、カルピスのような風味に近い珍しいお酒。甘いので後半に飲むとよい。うちらは酔っ払って訳がわからなくなってきたときに口直しのように飲んでた。そういう飲み方をするものかどうかはわからないが、飲みやすいのでついつい飲んでしまい、さらに酔ってしまう典型的なお酒。同期飲みは記憶をなくす感じで飲む。この歳になってこんな飲み方する相手いないなと思うとまた楽しい。

pipe による関心の分離

go の Pipe は go 言語プログラミングのよいところをいくつか同時に実感できる、実用度の高いユーティリティだと思う。go 言語は writer や reader をインターフェースとして定義している。細かいメソッドの違いでいくつかの writer/reader インターフェースを区別していたりもするが、基本的には write/read のメソッドをもっていればよい。

type Writer interface {
	Write(p []byte) (n int, err error)
}

type Reader interface {
	Read(p []byte) (n int, err error)
}

この汎用的な writer/reader のインターフェースに従っていれば、さまざまな要件に対して抽象化ができる。オンメモリでデータを扱うこともあるし、OS のファイルシステム上のファイルとして扱うこともあるし、オブジェクトストレージ上のオブジェクトとして扱うこともある。そういった具象オブジェクトの如何によらず writer/reader のインターフェースに従うことでデータ処理とリソース管理を分離できる。これはプログラミングパラダイムにおける 関心の分離 と呼ばれていることを実現する。

バッチ処理において複数データを読み込みながら処理を行い、処理中にエラーが発生したら、そのエラーデータを特定のファイルに書き込みしたい。エラーが発生したときだけ特定ファイルを生成する。そういった要件は次のように実装できる。

func write(w io.WriteCloser) {
	defer w.Close()
	/*
		if true {
			return
		}
	*/

	records := [][]string{
		{"first_name", "last_name", "username"},
		{"Rob", "Pike", "rob"},
		{"Ken", "Thompson", "ken"},
		{"Robert", "Griesemer", "gri"},
	}

	cw := csv.NewWriter(w)
	for _, record := range records {
		if err := cw.Write(record); err != nil {
			log.Fatalln("error writing record to csv:", err)
		}
	}
	cw.Flush()
	if err := cw.Error(); err != nil {
		log.Fatal(err)
	}
}

func read(r io.ReadCloser) {
	rr := bufio.NewReader(r)
	line, _, err := rr.ReadLine()
	if err == io.EOF {
		return
	}
	/*
		writer, err := os.Create("./test.txt")
		if err != nil {
			log.Fatal(err)
		}
		defer writer.Close()
	*/
	writer := os.Stdout
	if _, err := writer.Write(append(line, []byte{'\n'}...)); err != nil {
		log.Fatal(err)
	}
	if _, err = io.Copy(writer, rr); err != nil {
		log.Fatal(err)
	}
}

func main() {
	r, w := io.Pipe()
	go write(w)
	read(r)
}

Pipe を使うことで読み込みの処理と書き込みの処理を分離できる。ここでいう読み込みはバッチ処理、書き込みはエラーデータの保存処理に相当する。普通に実装すると、バッチ処理中にエラーデータの保存処理も記述することになる。業務のコードだと本質ではないリソース管理が煩雑になってしまう。2つの処理に分割するため、どちらか一方は非同期で実行する必要がある。ここで goroutine のシンプルさが際立つ。さらに pipe を使えば、reader 側は writer が Close するまでブロックするから終了処理の同期も自然なコードで実現してくれる。

非同期/並行処理の難しいところを go のインターフェースによる抽象化、簡潔な goroutine 記法、pipe に隠蔽された自然な同期処理の3つで簡潔に表現している。複雑さに対してこれほど簡潔なコードはそうそうないと思う。このコードを他の言語で実装しようとしたらもっと複雑になるはず。このサンプルコードとともに若いメンバーにリソース管理のリファクタリングをコードレビューで指摘したものの、そのメンバーには理解できなくて実装できなかった。そして、その処理を私が作り直すことになったというのが、今日のお仕事だった。本当は難しい非同期/並行処理をいくら簡潔にしても、その経験がないと人間には理解できない。まさに本質的複雑さを表しているように思える。

つなぎの雑多な一日

昨日の続きでメンバーのコードレビューをしながら、openldap スキーマの validator のバグ修正をしつつ、チーム勉強会で雑多な仕様や設計の議論をしていたらそれにも時間を取られて、1日としてはまあり開発を進捗させられなかった。コードを書くことに集中できる時間を2-3時間連続で確保できないとモチベーションにも影響が出てくる。それだけ取り組んでいる課題が複雑だったり煩雑だったりしているのもある。

集中力の正体

昨日は20時半頃にお仕事を終えて帰った。スーパーで買いものして、家に帰って晩ご飯を作って食べて、休んだらオフィスへ戻るつもりがそのまま寝てしまった。

メッセージ作成

年一ゲストとして出演している terapyon channel100回記念公開収録 があった。いつもお世話になっているので私は参加できないがカンパ枠で応援していた。昨日てらださんから音声メッセージを作ってほしいと依頼され、本当は昨日の夜に作ろうと思っていたものの、疲れて寝てしまったので朝起きてから慌ててオフィスへ行って作成した。ubuntu の apt でインストールできるツールとして audacity を使って録音した。簡単に使えた。お祝いのメッセージを文章に書き出して、マイクテストも含めて最終的には7回撮り直した。文章を書いてから、話してみると、流れが悪かったり、語呂が悪かったり、途中で詰まったり、かんでしまったりと、なにか気になるところをみつけて、文章を推敲して録音しながら1時間ほどやってた。録音して聞き直すと、いかに自分の話し方が下手であるかがよくわかる。話し方を練習しないとうまくならないのだろうな。

みなとのもりの運動

前回の所感 。14時頃に作業を終えてストレッチまで1時間ほど時間が空いた。ふと運動してからストレッチへ行くとよさそうだと思い立ってジョギングと縄跳びをしてきた。前回が7月24日なので1ヶ月半ほど運動していなかった。1kmほどジョギングしてから縄跳びを15分間した。以前と比較したら軽めに流したにも関わらず、久しぶりに運動したら体力が落ちていてかなりバテた。習慣的に運動していないとすぐに衰える。久しぶりに運動できたので、また再開するきっかけになるかもしれない。

ストレッチ

今日の開脚幅も開始前148cmで、ストレッチ後152cmとあまり数値は出なかった。直前に運動して行ったのでよく伸びるかな?と予測したものの、測ってみるとそうでもなかった。それでも運動した後にストレッチを受けるのはカラダによさそうにも思える。ここ1ヶ月半ほど運動できていないものの、体重は67kg台を維持していて体脂肪率や筋肉量はあまり変わっていない。トレーナーさんとそういう話をしていたら、筋肉は落ちにくくつきにくいから1ヶ月ではそんなもんとのこと。

プログラミングの集中力と生産性への考察

ストレッチを受けているときにトレーナーさんと話していて思うことがあったのでまとめておく。いまの開発フェーズは開発者としてプロジェクトに参加している。平均すると1日10時間ぐらいお仕事していて、早く帰る日もあるから遅い日は日付が変わるぐらいまではコードを書いていることがある。家に帰ると休んでしまうからオフィスで晩ご飯を食べることも多い。なぜプログラミングに集中していると運動ができないのか?を考察してみた。

  • 他の余分なことを排除すると集中力が増す

いまやっている開発のお仕事は1日で完了するような簡単な開発ではない。1年半開発しているので残っている課題は厄介で複雑な問題への対応であることが多い。新規機能を追加するときも既存機能や仕様や依存関係を考慮しないといけない。そうすると2-3日かけて課題を解決することが多い。そのときに他の余分なことを排除した方が脳のリソースを課題解決に割り当てられる。通勤しているときも、寝ているときも、ご飯を食べているときも取り組んでいる課題のことを四六時中考えている。他のことに脳のリソースを割くと課題解決の品質が下がる可能性が高い。ずっと考え続けることが複雑な問題の課題解決に重要となる。

  • システム開発とはタイムアタック競技である

システム開発のプロジェクトマネジメントにおいてもっとも重要な概念はタイムボックスだと私は考えている。適度な期間 (うちは2週間) を設け、モノゴトに取り組む最初と最後を作ること。認知心理学の研究からも記憶の仕組みからも期間の最初と最後がもっとも学習効果が高いことを示唆している。このことは私の経験則においてもシステム開発で当てはまる。作業期間が適切でないと人間はだらだらしてしまう (パーキンソンの法則) 。プログラミングにおいて動くのは当たり前で、動いた上でいかに品質をあげられるかが腕の見せ所になる。エラー処理を適切に制御できているか、他人がコードを読んでも理解しやすく保守しやすいか、将来の拡張性を考慮して設計されているか、など品質をよくするための取り組みには答えがない。仕事ができる・できないを分ける行動の1つとして、答えのない問いにどれだけ準備できるか、考え続けられるかと言い換えられるかもしれない。答えがないからいくらでも品質をあげるための施策がある。しかし、現実の業務には期限があるため、期限内に最善の品質を目指すことになる。そのため、タイムボックスでシステム開発をマネジメントする限り、いつもタイムアタック競技をしているのと同義になるから時間が足りない。

プログラミングはいつも妥協を強いられる。完璧で最高のシステムなどありえない。その時々で開発途中における最善の動くものをスナップショットとしてバージョン管理しているに過ぎない。この妥協するレベルが私にとってはマネージャーと開発者という役割で大きく異なるのだろうと思う。マネージャーとしての遊撃なら先送りできても、開発者としては多少の負荷があっても解決してしまう。自身の基準を満たす働き方に違いがあるのではないかと思う。言語化してみると、この考え方はプログラミングに限らず、そのときに取り組む対象によって何にでも応用できそうに思う。

ldap スキーマの情報を返す web api の実装

一昨日昨日 と ldap スキーマの parser について調査して実装した。最終的には構文解析した値をそのまますべて返すわけではなく、実際に ui や内部の処理で必要な情報のみを使いやすい構造にして web api として実装する。マージリクエストを作成してメンバーにコードレビューをしてもらっていて、スキーマ情報だけでは足りない仕様に1つ気付いた。たとえば、次の cn という属性の定義には SUP (super の略語で親の定義から派生していることを表す) として name が定義されている。

attributetype ( 2.5.4.41 NAME 'name'
       EQUALITY caseIgnoreMatch
       SUBSTR caseIgnoreSubstringsMatch
       SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )

attributetype ( 2.5.4.3 NAME ( 'cn' 'commonName' )
       DESC 'RFC2256: common name(s) for which the entity is known by'
       SUP name )

cn の定義には SYNTAX は定義されていないが、これは name の定義にある SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 (Directory String のこと) の oid を継承するという仕様になっている。自分で SUP を調べて継承元の情報を取得するのは面倒なので構文解析した後に SUP が指定されていれば Equality, Ordering, SubStr, Syntax を親から取得するように改善した。こういうところは自前で parser を実装しているので自分たちの都合にあわせてカスタマイズしやすい。

3日もかかってしまったが、これで ldap スキーマを返す web api の実装を完了できた。いまの開発フェーズにおける、私が機能拡張する issue はこれで完了にしようと考えている。来週からはテストや qa 向けの作業にのみ注力していこうと思う。本番導入前に少しでも運用トラブルの懸念を減らす、もしくは品質をあげるための取り組みをあと3週間やってからしっかり検証をしていく。ここ1ヶ月ほど深夜までコードを書いていることが多かった。終わってみれば、面倒で厄介な issue も大半を fix した。当たり前の話だけど、四六時中ずっとコードを書いていたら気付いたら成果として積み上がってきた。マネージャーを移管したことでその分の労力を開発にまわせているのもある。

openldap スキーマと parser generator

この開発フェーズではあまり機能拡張を行わない方針としているが、できればやっておいた方がよい機能拡張の1つに openldap サーバーから ldap スキーマを取得する処理がある。ちょうどいま若いメンバーに実装してもらっている大きな機能のバリデーションにも ldap スキーマの情報があると便利そうなので私が対応することに決めた。

openldap サーバーで管理している ldap スキーマの定義は openldap サーバーの Schema Specificationrfc 4512 の2つをみると仕様がわかる。たとえば、次のように AttributeTypeDescription というスキーマの定義は Augmented Backus–Naur form (abnf) という記法で定義されている。rfc などのネットワークプロトコルの世界では abnf のフォーマットで仕様を説明することが多いらしい。

AttributeTypeDescription = "(" whsp
      numericoid whsp              ; AttributeType identifier
    [ "NAME" qdescrs ]             ; name used in AttributeType
    [ "DESC" qdstring ]            ; description
    [ "OBSOLETE" whsp ]
    [ "SUP" woid ]                 ; derived from this other
                                   ; AttributeType
    [ "EQUALITY" woid              ; Matching Rule name
    [ "ORDERING" woid              ; Matching Rule name
    [ "SUBSTR" woid ]              ; Matching Rule name
    [ "SYNTAX" whsp noidlen whsp ] ; Syntax OID
    [ "SINGLE-VALUE" whsp ]        ; default multi-valued
    [ "COLLECTIVE" whsp ]          ; default not collective
    [ "NO-USER-MODIFICATION" whsp ]; default user modifiable
    [ "USAGE" whsp AttributeUsage ]; default userApplications
    whsp ")"

AttributeUsage =
    "userApplications"     /
    "directoryOperation"   /
    "distributedOperation" / ; DSA-shared
    "dSAOperation"          ; DSA-specific, value depends on server

openldap サーバーに対して ldap スキーマを取得する方法は How can I fetch schema information from the server? の faq に書いてある。search base に対してサブスキーマサブエントリを返す dn を取得する。openldap サーバーの場合 ldap の root ツリーに対応するのは cn=Subschema がデフォルトとなる。

$ ldapsearch -H ldap://localhost -x -LLL -b dc=example,dc=com -s base subschemaSubentry
dn: dc=example,dc=com
subschemaSubentry: cn=Subschema

この dn にスキーマ情報の属性が格納されているのでそれらを取得する。このスキーマ情報は operational attributes として管理されているので + という記号が operational attributes 属性群をまとめて取得するキーワードになっている。

$ ldapsearch -x -LLL -b cn=Subschema -s base '(objectClass=subschema)' +

これでスキーマ情報を取得できる。先に書いた AttributeTypeDescription の実際のスキーマ情報は次のような内容になる。こういったスキーマのテキスト情報をたくさん取得できる。

( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )

go の parser generator 調査

この手のものは abnf から parser をコード生成するのが一般的なやり方かな?と、まずは go で使えそうな parser generator について調べてみた。意外と go 製の parser generator はみつからなかった。私が発見できたのは次の3つぐらい。

最後の antlr は java 製のツールだけれども、go のソースコードを出力できるので go 実装の parser をコード生成できるという意図で対象としている。goyacc は abnf の文法を yacc に変換しないといけない。yacc で実装した ldap スキーマの parser を公開しているのもみかけたが、yacc は違うなと思って除外した。antlr も abnf から専用の文法に変換しないといけないから不採用にした。

それで消去法的に bnf (ebnf) を扱える gocc で parser をコード生成できないかを調査してみた。しかし、半日ほど bnf を書いて実際にコード生成してみて不採用とした。ebnf の options 記法として [...] に gocc が対応していないようにみえた。この記法がないと ldap スキーマの文法定義が煩雑になる。issue のコメント をみると、lexer は対応したが、parser は対応していないといったコメントがある。コード生成しようとするとエラーになるので未対応なのかもしれない。bnf でこの options 記法相当のものを自分で文法定義すると、1つ2つなら簡単に変換できるが、数個の組み合わせがあると途端に文法の複雑さが大きくなるように思える。abnf から bnf に変換する過程で文法が複雑になってしまうとその後の保守ができなくなる懸念が生じるので断念した。

今回の gocc の採用は却下したが、軽く触ってみて gocc 自体の感触はよかった。シンプルな bnf で表現できるものであれば機会があれば採用してもよいと思える。gocc example をみればわかるが、bnf を書きながら単体テストのコードを実行して動作検証を小さく簡潔にできる。これは parser のコード生成の開発サイクルは速くできそうに感じた。こういう小さく単体で動くツールは好み。

最終的な結論としては parser generator は使わず、自前で parser を実装することを決めた。

まる一日コードレビュー

昨日は2時頃に帰ってきて、朝も昼も食べてなくて空港でおにぎりを食べただけだったので家に戻ってきてお腹が空いて軽食を食べてから寝た。お手伝い先の都合で朝から請求書を送る必要があったため、8時過ぎにはオフィスへ行って請求書を作成して送付した。9時からはらさんと隔週の雑談もあったのでスケジュール的にも早起きする理由があってちょうどよかった。

出張帰りの気分転換に nginx で X-Forwarded-For を設定する方法を remote_addrとかx-forwarded-forとかx-real-ipとか をみたりしながら調査していた。リバースプロキシがある前提であれば、api サーバーがそのヘッダーを参照するのではなく、nginx がそのヘッダーを remote_addr に設定するようにしてしまおうと思う。

今回の開発フェーズでは若いメンバーにサーバーサイドの大きな機能を開発してもらう。サーバーサイド開発の経験を積んでもらう機会にもする。レビューに私の時間が取られることを折り込んで工数を確保している。ある機能の初期実装ができたというのでそのレビューをしていた。そのコードレビューをしていてサーバーサイドの開発は難しいなと実感した。経験がないメンバーだと動けばいいコードしか書けない。効率やリソースの消費を考えながらコードを書いていないし、ライブラリやフレームワークのコードも読んでいないからサーバーサイドの運用に耐えるコードは書けない。あちこち指摘して直してもらう必要があった。なんやらかんやらでまる一日コードレビューしていて、自分の作業はまったくできなかった。

実際に動くようになってからわかること

東京出張しての、隔週の定例会議にて、ある仕様について話しをした。これまでもずっとそういう仕様ではあったが、実際の本番運用が近づいてきて、実運用を考えているうちにある要件が出てきた。しかし、現状のシステムの構成上、その要件は満たせないことがわかった。1年半にわたって開発してきて初めてこの要件は現状のシステムの制約でできないと発言した。出来ないものは出来ないで仕方ない。それでも今更そんな要件が出てくるところに課題管理として出来ていなかった反省もあるし、気付きが足りなかった。私は20年も開発してきたからたいていの要件への対応は出来るだろうと、自身の経験から過信してしまっているところもあったなと思えた。やはりお仕事になるエンタープライズの要件はなかなか難しい。難しいからお客さんはお金を払って si 的なお仕事になるとも言える。自分への情けなさとそれでもできる範囲でやっていかないといけないという現実とを実感したひとときだった。

生活のリズムづくりの1ヶ月

歯科検診

17時から歯科検診へ行ってきた。スタッフに「痩せてませんか?」と尋ねられて3ヶ月ぶりならそれほど変わらないんじゃないかと返したが、そのスタッフが私の担当をするのは2月以来だったらしく、それならかなり変わりましたと話していた。たまにしか行かないのに、首まわりが全然違うと言われて、半年前の体型を覚えているんやなと思ってこっちが驚いた。たまに会う人向けのお断りとして「癌ではありません」と説明しといた。

生活のリズムづくり

ここ1ヶ月ほど開発者へ戻るための取り組みをしてきた。昼間にお仕事でコードを書いて、晩ごはんを食べてから、夜にコードを書くのが習慣的になってきた。概ね1日の作業時間が10-12時間になってきている。夜に開発できている日はだいたい20-21時頃から24時頃までコードを書いている。夜は割り込みが入らないので集中できる。今日も晩ごはんを食べて22時前から翌2時半ぐらいまでコーディングしていた。ファイルのアップロード/ダウンロードを扱う汎用 api を実装した。

いまの開発フェーズでは 過去の残課題 が多い。それはこれまでの私のマネジメント不備でもあるし、知見を得たことでアーキテクチャの不備や再設計のリファクタリングができるようになった課題も多い。技術的負債が溜まっているとみなせる。いずれにしても、実運用を間近に控えて直せるところをできるだけ直しておきたい。当初は私が改善する issue が仕様変更になるためにプロジェクト全体のボトルネックになってしまっていた。なるべく初期の開発で ボトルネックを解消 してからもペースを落とすことなく、集中して開発作業を継続できている。スケジュールの前倒しとまではいかないが、後手にまわっている印象はなく迅速に対応できている。個人的にもよいリズムと集中力が出てきたと感じている。その視点からみると、いま開発に向いている集中力をこれまでは体脂肪コントロールや運動に費やしてきたこともわかる。だから短期間に大きな成果が出た。人の可処分時間は決まっている。継続的になにかに取り組むと1ヶ月も経てば実感できるぐらいの成果がでる。もしかしたら私がフルタイムの開発者として作業できるのはあと3ヶ月になるかもしれない。悔いの残らないよう、できるだけ集中して取り組もうと思う。

ファイルアップロードのインターフェース

今日も昼間は普通にお仕事で開発して、お仕事を終えて晩ごはんを買い出しへ行ってきて、オフィスで晩ごはんを食べてから夜のコーディングに戻るといった一日になった。昼間は会議や問い合わせ対応、他のメンバーの進捗をチェックしたりなど、ちょくちょく割り込みが入る。夜はそれらがないことがわかっているので3-4時間集中してコーディングできる。

echo のファイルアップロードのサンプル

メンバーがファイルを受け渡しする web api のインターフェースがよくわからないということで私がサンプルを作ってみることにした。echo には File Upload のサンプルも紹介されているからすぐにできる。歴史的にブラウザからのファイルアップロードを前提にすると、ファイルを扱うときは次の Content-Type を使う。

Content-Type: multipart/form-data

これを echo のコードで書くと次のようなインターフェースとなる。

type MyRequest struct {
	Type      string    `form:"type" validate:"required"`
	Operation string    `form:"operation" validate:"required"`
}

func handle(c echo.Context) error {
	req := MyRequest{}
	if err := c.Bind(&req); err != nil {
		return err
	}
	if err := c.Validate(&req); err != nil {
		return err
	}
	file, err := c.FormFile("file")
	if err != nil {
		if errors.Is(err, http.ErrMissingFile) {
			return echo.NewHTTPError(http.StatusBadRequest, "file is required")
		}
		return err
	}
    ...

ファイル情報を MyRequest で管理できるとリクエストパラメーターを一元管理できて望ましいが、次の issue によると現時点ではそれはできないらしい。

curl コマンドでリクエストするには次のような cli になる。

$ curl -i -X POST -F type=misc -F operation=add -F file="@myfile.data" ...

寝ている間に再設計

昨日のもやもやは寝ている間に整理され、今日は標準的な開発者としての過ごし方になった感じがする。

マネージャーとの 1on1

これまでは私がマネージャーとして 1on1 をしてきたが、今後は交代したメンバーがマネージャーを務める。この開発フェーズでは私は開発者 (メンバー) として 1on1 をしてもらうといった感じかな。自分が開発者側だと思うと、いま私が取り組んでいることの話しをしやすかったりした。立場によって 1on1 のやり方は変わることを実感した。マネージャーはメンバーの話しを聞く方に注力したり、プロジェクト全体の話しをしがちになる。開発者として臨むと、自分の抱えている issue に集中して話せるといった違いがあった。

CPAP の中断

夜に睡眠外来へ行ってきた。以前に CPAP 装置 を借りて3ヶ月ぐらいやっていたものの、ここ2ヶ月ほどやらなくなってしまっていた。もともとは体脂肪コントロールの疲労回復のために始めたものだった。体脂肪コントロールがうまくいって目標達成したことで睡眠改善を行う目的もなくなってしまって関心を失ってしまった。借りているだけで毎月5千円ほどかかるので中断して機器を返却することにした。旅行のときなど、一時的に借りられないかを聞いてみたら、それは不可とのこと。また再開したくなったらいつでも言えばよいとのこと。

ツールの再設計

昨日の続き 。睡眠外来から帰ってきて、晩ごはん食べて、軽く飲みながら24時過ぎまでコードを書いていた。今日もまる一日ツールの再設計をしていた。昨日のノーアイディアな状況から少しずつ好転していって、ある処理で歯車が噛み合ってからはいつも通り着実に進捗していく様がみられた。意図的にやっていることではあるけど、課題について考えながら一晩寝ると、寝ている間に無意識に刷り込まれて脳が考えてくれる。それで徐々によい設計が現れてくるようになって明確な実装に落ち着いたのではないかと推測する。今日はよい感じに集中できた。