Posts for: #Refactoring

9月最終日に主開発を完了

今日のバドミントン練習はリフティングを10分した。連続最大回数は70回できた。シャトルを上に放り投げてラケットでキャッチする練習も10分やってみた。昨日よりは少しうまくなった気がした。それでも5-10回に1回ぐらいの頻度。なかなか難しい。

最後の厄介なリファクタリングを完了

一昨日から着手しているリファクタリング作業に朝から着手していた。一晩寝かせた効果だと思うが、昨日の夜にノーアイディアだった設計もさくさく進んでシンプルに実装できた。無意識の脳の働きがすごい。13時頃には実装を終えてマージリクエストを作るのに約1時間。なぜリファクタリングするのかの目的と意図をマージリクエストに書いた。最終的にはこの issue の対応は丸2日かけてコードを書き直した。これが早いのか遅いのか、もう私にはわからなくなってしまっている。昔の方が体力と集中力がある分、土日で終えられたかもしれないし、いまの方が経験がある分、昔よりも高い品質のコードを短い時間で書いているのかもしれない。

老いは誰にもやってくる 歳を取れば技は練れる 駆け引きにもたける だが圧倒的なパワーに対して対抗しきれない日は必ず来る

幻海師範

知人が「プログラミングは長い間やっているとわかるようになる」と表現していてその通りだなと私も思う。プログラミングに限らず習熟によってスキルアップする対象はだいたい同じなのかもしれない。いまバドミントンの練習を始めたばかりだが、1年後にはいまより少し上手くなっていると嬉しい。

1ヶ月ぶりの休日出勤

1ヶ月ぶりの休日出勤

今日のバドミントン練習はリフティングを昼間に10分、夜に10分、壁当てを10分した。連続最大回数は82回できた (昼間) 。昼間にやってみたら夜に街灯の隣でやるときとの違いに気付いた。ほんのそよ風でもシャトルは風の影響を受けるので空中で少し流れたりする。その微妙な変化が暗いとみえにくい。昼間にリフティングをやってみたことで真っ直ぐ落ちてこず微妙に風で流れていることを観察できた。あとシャトルを高くあげた方が重力による加速がつくので真っ直ぐ落ちてきやすくなるかもしれない。

ポータブルネット

外でバドミントンができる準備をしていく。調べてみたらいくつか製品の候補がみつかる。価格帯は3千円から1万円ぐらいの幅。最終的には YONEX の ポータブルネット(バドミントン用).AC334 に決めた。なにかの記事で初心者は製品の良し悪しの判断が難しいから YONEX を買っておけば間違いないと書いてあった。amazon で購入すると9千円ほどだった。土日の昼間に公園へ持っていって練習できるかもしれないし、バドミントン設備がない体育館を借りて練習するのもよいかもしれない。たまたま組み立てしている動画をみつけた。一緒にクリップを用意しておけば幅の調節もできるみたい。

お昼からずっとコードを書いてた

昨日は家に帰る途中でバドミントンの練習場所を探したり、軽く練習をしたりしていて、1時過ぎに帰ってだらだらして3時頃に寝たせいか、少し寝坊してお昼頃にオフィスへ行った。ちゃんと休日にオフィスへ行ってお仕事できるぐらいには体調 (モチベーション) が回復した。病気だったわけでもないが。本当は今日中にマージリクエストまで作りたかったが、ラストワンマイル的なところがなかなか煩雑で24時過ぎまでやって明日に持ち越すことにした。寝ているときに無意識で設計するから明日にした方がコードの品質も上がるだろうと、既存のコードのロジックの確認や設計のヒントになりそうなことの調査をして終えた。

日曜日にお仕事をしたのは1ヶ月ぶり。しかも12時から24時と途中で晩ご飯休憩をはさみながらほとんどフルタイムに働いていた。他人のコードのリファクタリングだから私が直さないといけない積極的なインセンティブがない。この開発フェーズの最後の issue だったのと、私がコードレビューして知ってしまっているからやる・やらないを選択しないといけない。やらなくても誰からも非難されないかもしれない。逆に言えば、だからこそ、やらないといけないなと昨日、決心した。覚悟したからどれだけ労力をかけようが気にならなくなった。そんな休日のモチベーション管理。

バドミントンのひとり練習

今日のバドミントン練習はリフティングを30分した。連続最大回数は27回だった。

最後の厄介なリファクタリング

10時頃に起きて11時にはオフィスへ来て作業をしていたと思う。昨日ジョギングと筋トレしたから寝起きジョギングはやめて普通にオフィスへ行って作業していた。この issue はどこまでリファクタリングするかの決めの問題もあり、少し寝かせてあった。私のもてるスキルを駆使して思い切りやることを決めて土日で大半を終わらそうと思う。いくつかの葛藤と逡巡を振り払って着手はした。昼間は既存のコードの本質的な問題の分析、再設計のための要項の調査、軽くコードを書きながらの感触の確認などをしていた。その後、ストレッチに出掛けて戻ってきてから続きをやるつもりが、いろいろ雑務をやっていたら22時ぐらいになってしまってまた明日でいいやと諦めた。モチベーションが足りない。一回休み。

ストレッチ

昨日、久しぶりに筋トレをして夜は全然平気だったのに寝て起きたらあちこち全身筋肉痛でストレッチもかなりきつかった。悪い痛みではないから筋肉をほぐしてもらった感じになる。今日の開脚幅は開始前146cmで、ストレッチ後153cmだった。筋肉痛だから硬めのように思えたが、数字だけ比べたら普段とそんなに変わらなかった。

トレーナーさんと自民党の総裁選挙の話題になって、一通りニュースをみて私も思うところはあったものの、政治的な話題のやり取りする場所がないから誰と話すこともないだろうとしまいこんでた矢先、トレーナーさんから話題が出てきたのでいろいろ話してみた。石破さんは自民党内で嫌われているから決戦投票で勝てないだろうという大方の見通しを覆したのは見事だったし、石破さんは良識なリベラル、且つ金融所得課税の強化に前向きだったりもするから市場の反応は悪い。これまでは総理大臣が変わったタイミングはご祝儀相場のように歓迎されることが多かったのに対して、月曜日は株価が急落するようにみられている。これほど市場からネガティブな反応が出るのも珍しいなという所感。余談だが、私はいまどちらのポジションもとっていないので株価が下がってもあまり影響はない。個人的にはシステムへの理解が深いという側面だけで河野さんを応援しているものの (一方で官僚に対するもの言いがパワハラ的なところがマイナス)、今回は後ろから2番目と不人気だった。日本では自民党総裁=総理大臣なわけではあるが、政治家が影響力をもつタイミングというのは本当に難しい。石破さんも5回目の総裁選ということで悲願が叶ってよかったんじゃないかと応援したい。

ひとりでできるバドミントンの練習

ストレッチを終えてから 購入したラケットを受け取り してきた。試し打ちではないけど、買ったばかりで使ってみたいから KALIDIA チャンネルから1人でできる練習を探してみた。次の練習はラケットとシャトルとの距離感を掴むための練習になるという。なるべく日課としてしばらく続けてみたい。

  1. シャトルのキャッチ
  2. バドミントンの技を3つ行う
    • スピンネット
    • ミニクロスネット
    • ロビング
  3. リフティング
  4. 屋外で向かい風に向かってロビングを打つ

プログラミングとバドミントン

よく働き、よく遊ぶみたいな一日だった。

リファクタリングの最後の集中力

昨日から集中力を維持してずっとリファクタリングしている。やや複雑な機能をリファクタリングしているため、今週いっぱいはかかる見通し。本当は私が一から書き直した方が早い。しかし、若いメンバーに指導する意図もあるため、既存のコードを読んで誤りを訂正したり、仕様を確認したりしながら手直ししていく。射撃しつつ前進 の成果は着実にみえてきて、しんどいけど、コードの品質はどんどん改善している。マネージャーと 1on1 でリファクタリングの状況も伝えながら開発のイテレーションをもう1つ増やした方がスケジュール的にはよさそうかなぁみたいな共有もしていた。

体育館でバドミントン

前回の所感 。17時でお仕事を終えて最初で最後?の しみん福祉スポーツセンター の体育館を借りてバドミントンをしてきた。18時から21時の3時間枠で料金は3,000円。磯上体育館が2時間で700円なのと比べると割高になる。しかも、磯上体育館の方が駅に近いため、他地域から来られる方も参加しやすい。今後はこのスポーツセンターの体育館を借りるのはやめようと考えている。体育館予約にはリスクがあって3ヶ月前に予約しないといけないため、予約しても本当に参加者が来るかどうかわからない。一番悲惨なのは予約したけれど、誰も来なかったというパターン。いまのところ、そういう状況は発生していない。

閑話休題。今日は新しい人も2人きて全部で5人参加した。初めて行ったスポーツセンターの体育館は広くて設備そのものはとてもよかった。

前回来られた方に教えてもらった KALIDIAバドミントンチャンネル の動画を ipad にダウンロードしてもっていった。体育館で参加者と一緒にみながら次の練習をやってみた。動画をみたら簡単そうにみえるけど、実際にやってみると、狙ったところに打てないことに気付く。こういう地道な練習をしないとスキルが上達しない。もっと練習のパターンを増やしていきたい。

初めて来られた若い方がテニスをずっとされていたらしく、バドミントンも上手だった。前回もうまい人が来てくれて練習方法を教えてくれた。うまい人のプレーをみていると、あんな風にできたら楽しそうとか、思い通りにラケットやシャトルの操作ができなくて悔しいとか、感情的にモチベーションがあがってきた。あと久しぶりに運動して汗をかいてよい気分転換になった。

業務としてのリファクタリングへの集中力

今週からメンバーが開発した、少し大きめの機能の品質改善のためにリファクタリングをしていく。本当は週末に進めておこうと思っていたものの、どうにもやる気がなくてまったく手をつけられていなかった。不思議なもので休日は朝起きられなかったりオフィスへ行ってもまったくコードを書けなかったのに、平日なら普通に朝8時までに起きてオフィスへ行って集中して作業できた。2日休んでいるので体力を回復していたというのはある。それでも休日と平日の業務としてのプログラミングへの集中力の違いは何なんだろう?とやぎさんに相談したら 反脆弱性[上] という本にその答えが書いてあったと教えてもらった。この本を読まないとその答えは得られない。しかし、いまの私は本を読む元気がない。誰か読んだ人がいたら私にわかりやすく教えてほしい。

私が書いたコードに対する不備や欠陥は責任感からリファクタリングをする一定のモチベーションになる。しかし、他人が書いたコードのリファクタリングはそのモチベーションがいくらかレベルダウンするのではないか?という仮説もある。他人のコードを読んで理解するのはコストがかかるし、本質的には他人のコードを読むことはできても理解することはできない。コードの出典に起因するストレスもあるのかもしれない。

case-insensitive という古くて厄介な問題

casemap によるリファクタリング

ldap プロトコル (v3) は rfc 2251 で定義されていて属性へのアクセスは case-insensitive (大文字小文字を区別しない) に扱うという仕様になっている。

ldap サーバーへの問い合わせや検索は case-insensitive となるため、ldap エントリーを扱う他システムの処理もすべて case-insensitive にしないと整合性が取れなくて混乱する。これは利用者だけでなく開発者も同様であり、一部が case-insensitive なのに、一部を case-sensitive (大文字小文字を区別する) にすると、比較処理がマッチしたりしなかったりという混乱を生じる。そういう面倒くさい issue が過去にいくつか散見されてきた。この問題は場当たり的な修正ではなく、本質的な対応が求められた。

結論としては、すべてを case-insensitive に扱う必要があって、そのためにキーを case-insensitive として扱う map like なデータ構造を実装した。オリジナルのキー情報も保持しつつ、case-insensitive にアクセスできるよう小文字変換しておいた名前変換テーブルを内部にもつ。最初からこういったデータ構造を定義しておけば開発時に混乱は起きないが、こういうものは後になってわかってくる。うちは1年半経ってからこの問題の本質を理解した。そのため、関連する処理のインターフェースを見直して置き換えることになった。こういうリファクタリングは時間がかかる割に成果も地味なので労力に対して評価されないことが多い。私もやっていてあまり楽しくはないが、こういうところをきっちり作ると品質に寄与するのを経験的に知っている。

type CaseMap struct {
	keyValue map[string][]string
	nameConv map[string]string
}

func (m *CaseMap) String() string {
	return fmt.Sprintf("%v", m.keyValue)
}

func (m *CaseMap) Values(name string) []string {
	origName, ok := m.nameConv[strings.ToLower(name)]
	if !ok {
		return nil
	}
	values, ok := m.keyValue[origName]
	if !ok {
		return nil
	}
	return values
}

func (m *CaseMap) Get(name string) ([]string, bool) {
	origName, ok := m.nameConv[strings.ToLower(name)]
	if !ok {
		return nil, false
	}
	values, ok := m.keyValue[origName]
	return values, ok
}

func (m *CaseMap) Set(name string, values []string) {
	m.keyValue[name] = values
	m.nameConv[strings.ToLower(name)] = name
}

func (m *CaseMap) Len() int {
	return len(m.keyValue)
}

func (m *CaseMap) Iter() iter.Seq2[string, []string] {
	return func(yield func(string, []string) bool) {
		for k, v := range m.keyValue {
			if !yield(k, v) {
				return
			}
		}
	}
}

type CaseName struct {
	Original  string
	LowerName string
}

func (m *CaseMap) IterWithLower() iter.Seq2[CaseName, []string] {
	return func(yield func(CaseName, []string) bool) {
		for lowerName, origName := range m.nameConv {
			name := CaseName{
				Original:  origName,
				LowerName: lowerName,
			}
			if !yield(name, m.keyValue[origName]) {
				return
			}
		}
	}
}

func (m *CaseMap) ToMap() map[string][]string {
	return m.keyValue
}

func New(length int) *CaseMap {
	return &CaseMap{
		keyValue: make(map[string][]string, length),
		nameConv: make(map[string]string, length),
	}
}

func NewFrom(attr map[string][]string) *CaseMap {
	m := New(len(attr))
	for k, v := range attr {
		m.Set(k, v)
	}
	return m
}

2023年最初の記事は事例紹介

0時に寝て7時に起きて8時前までだらだらしていた。昨日は開発してないけれど、なんか疲れて起き上がれない。

先週末に恒大集団が米国で連邦破産法適用を申請というニュースが出ていたので香港市場でひと波乱あるのでは?という憶測も出ていたけど、とくに変わりなく1日が終わった。別の不動産大手もデフォルトの危機らしくて、その2回目の期限が1ヶ月ほどあるようなのでもう1ヶ月してから波乱があるのかもしれない。

最後のリファクタリング課題

このマイルストーンで開発を終えて次マイルストーンからテストへ移行する。機能拡張はほとんど終わっていて、作り直した方がよいリファクタリングのチケットをいくつかやっている。そのうちの1つに azure 連携するときに rest api を直接使っていて sdk を使っていない。microsoft 社も go の sdk を提供している。将来的には型によるバリデーションの仕組みを導入したいと考えている。あらかじめ sdk を使ったコードに置き換えておきたい。sdk が使いやすい api になっていれば、すぐに進捗するところが、これがなかなか、この sdk の api 設計もコードもあまりきれいなものではない。一方で大半のコードはスキーマからコードを自動生成しているようもみえるのでモジュールの構造を理解してしまえば、api の設計も類推は効くようになると思う。

ユーザー/グループの取得周り、ユーザー作成の web api 呼び出しを置き換えて疲れて晩ご飯を食べに帰ってしまった。家に帰ったらもうダレてしまってそのままだらだら休んでいた。残りの開発を明日に先送りにする。疲労もあるのか、モチベーションコントロールがうまくいかなくて、昔だったらあと2-3時間作業して帰るところのひと踏ん張りがきかなくなりつつある。気を引き締めないとあまりよくない。

事例紹介

昨日更新したたたき台 を最終版にするつもりが、午前中、先方に送る前に読み直したら細かいところに気付いてちょっとだけ推敲した。午前中にレビュー依頼を出して、夕方には返信がきてそのまま OK が出て、事例紹介を公開した。会社のサイトをすっかり触らなくなってしまっていて、8月になって2023年初めての更新になる。何度も書いているけど、この事例紹介は私の自己満足で、これを書くと世の中の役に立っている気がして嬉しくなる。さらに今回はプロジェクトマネージャーとして実績を出すことができたので次のキャリアへの大きな一歩となる。今後も課題管理の探求をがんばっていく。

設計の見直しとリファクタリング

2時ぐらいに寝て起きたかどうかあまり覚えていないが7時に起きた。あまり寝た気がしない。

エージェントアプリケーション開発

昨日の続き 。一晩寝かした甲斐があったのかどうか、意識的にはわからないが、少なくともつまづくことなく1つずつ設計の見直しや処理をパラメーター化していって一通りプロトコルの差異以外のところは整理できた。今日は自分のコードを書く余裕はなかったけれど、1日かけて既存の設計の見直しを終えられたことをうまくいったと自分で肯定しておこうと思う (他人からみたらそうじゃない可能性はある) 。

関数は1つの機能のみを実装して、入力しか依存関係をもたないようにシンプルにすべきという基本概念がある。その基本概念をちゃんと理解できていないと、設計のアンチパターンの1つとして、渡すパラメーターがどんどん増えていってしまうことに気付いた。まずは既存の処理やフローを変えずに設計を見直したのでそうなってしまっている。また後で時間があったら、インターフェースを明確にして根本的な設計を見直してもよいかもしれない。

go の channel 制御の学び

0時に寝て2時に起きて5時に起きて6時に起きた。久しぶりに夢をみたような気がする。

go の channel とクローズ制御

rabbitmq/amqp091-go を使った pubsub の consumer の実装で次のような for ループでメッセージを取得していた。この場合 deliveries の channel が閉じられると内側の for ループが終了して、外側の for ループの接続処理へ遷移する。

for {
    // コネクションの接続処理

    for d := range deliveries {
        // メッセージ処理
    }
}

この処理を context を使ってキャンセルできるよう、次に書き換えた。channel の Receive operator のドキュメントによると、channel は2値を返すことができて、戻り値の2番目の ok の値を調べることでその channel がクローズされているかどうかを判定できる。この仕組みを知っていれば select で ok を調べてエラー処理を実装できる。そうしないと deliveries の channel が閉じられれたときに select では常にゼロ値のメッセージが返るようになって意図したメッセージ処理とならない。

for {
    // コネクションの接続処理

    for {
        select {
        case <-ctx.Done():
            // context がキャンセルされたら終了処理
            if err := s.ch.Cancel(cid, false); err != nil {
                return fmt.Errorf("failed to cancel channel: %w", err)
            }
            return nil
        case d, ok := <-deliveries:
            if ok {
                // メッセージ処理
            } else {
                // エラー処理
                break
            }
        }

        // コネクションが閉じていればループを抜けて再接続
        if s.conn.IsClosed() || s.ch.IsClosed() {
            log.Info("the session was closed, try to reconnect", nil)
            break
        }
    }
}

同じ channel からメッセージを取り出すループ処理でも用途によって使い分けがあるなぁと今更ながら学んだ。というか、自分でバグを埋め込んで自分で直した (´・ω・`)

マージまで約1ヶ月

先日送った amqp091-go の pr が最終的にはほぼそのままマージされた。約1ヶ月ぐらいの議論やレビューがあって取り込まれた。新しいリリースバージョン (次は 1.9.0?) が出たらうちのアプリケーションでもこの新しいメソッドを使うようにして実績を積ませる。この pr は「あったら便利」程度の機能なのでそれほど重要ではない。

この成功体験をもって次は本命の go-ldap の pr のマージに向けて勢いをつける。こっちの pr は業務に直結しているので本気でやり取りしている。

コードを書いていると後から改善点に気づく

1時に寝て7時に起きた。起きてからドラクエタクトをだらだらやってて10時前に起き上がってそれからオフィスへ行って活動してた。

リファクタリングのリファクタリング

昨日書いたマージリクエストの変更点についてドキュメントの更新だけしようと作業していたら、その後、一緒に修正した方がよい追加の機能拡張に気付いて3時間ほど追加の実装をしていた。その後、お昼ご飯を食べているときに午前中に書いた実装を改善した方がよいところに気付いて、さらに追加で1時間ほどリファクタリングしていた。コードを書いていて、一旦はできたつもりになってその時点ではよさそうに思うのだけど、時間が経ってから考え直すと考慮漏れやもっとよい実装のアイディアを思い付いたりする。私の頭が悪いだけかもしれないが、最初からよい実装や設計を行うことは多くの人にとって難しいことだと思いたい。何度も考えて作り直したり改善したりしているうちにもっとよい方法に気付く。

多くの開発者はもっとよいアイディアを思い付いたとしても実際にリファクタリングをしようとしない。動いているコードを修正して壊れるリスクや他に優先度の高い作業があるとか、いろいろやらない理由を言う人たちもいる。私はそのやらない理由を議論している合間にリファクタリングしてしまう。というのは、誇張した比喩で実際にはもっと時間がかかることもあるけど、設計も見直す数千行レベルの変更を気軽に行う。もしそれで壊れたらどうすると聞かれてもシンプルに謝るだけ。謝ってから直す、テストを書く、再発防止のための仕組みを考える。やることはそれだけ。問題のあるコードを見て見ぬ振りをする開発者の方が普通という感覚がある。10年以上開発をやってきて思うこととして、そういう価値観を上位の開発者が壊していって模範を示すことがよい開発文化への第一歩となる。しんどいことに対して口だけでは人は動かない。個々の開発者が問題だと思ったらどんどん書き直していく、リファクタリングしていくという文化は一朝一夕ではできない。そういう開発文化を醸成していくと大きな技術的負債が溜まるといったことはなくなるのではないかと考えている。

リファクタリング一段落

1時に寝て7時に起きた。前日も21時頃までオフィスにいて、今日も午後からコードを書いていて22時ぐらいまでやってた。コードを書いていると時間がどんどんなくなる。本当は三宮.dev の勉強会があったんだけど、このリファクタリングは週末にやってしまわないとやばいという野生の勘でキャンセルした。

ストレッチ

今日の開脚幅は開始前156cmで、ストレッチ後160cmだった。だいたいいつも通り。先週から右すねの外側の筋に張りがある。先週よりちょっとよくなった気もするけれど、まだ張りが継続している。あと今週は右腰に張りがあった。今週はリファクタリングしていて座ってきる時間がいつもより長かったためだろうと推測する。どんなに忙しくてもストレッチだけは休まないようにしている。ストレッチに通い始めて2年以上経つが身体的に体調が悪いということは記憶にほぼない。日記にはその週のどこそこに張りがあるとか調子が悪いといったことを書いたりしているが、それは日常生活を送る上で支障が出るようなレベルではない。そうならないように予防している。健康を維持する上でストレッチは大きな影響を与えているため、中長期の展望から忙しくても継続するようにしている。

機能拡張とリファクタリング

今日は休日出勤して go のコードを書いていた。ある機能を作るときに内部的には汎用の api にしてしまって他のコレクションのデータ型でも再利用できるようにしたい。先週ずっと go の generics を使って mongodb 周りのコレクションとそのクライアントのリファクタリングをしていた。go の generics の理解も進んで crud なインターフェースを generics でどのように定義して実装すればいいかわかってきた。その過程で web api のアプリケーション層と mongodb のインフラ層 (データ層) の役割分担も明確になりつつある。どちらも generics を駆使して型チェックされた上でソースコードを共通化し、汎用 api としてリファクタリングしながら設計している。その集大成としてアプリケーション側で汎用的な機能を追加するときに、理想的には1つのコードを追加・変更すれば、別のデータ型でもすべて同じように動くといった機能として実装した。4つのコレクションのデータ型で同じ振る舞い (機能) を共通化する。その実装も丸1日やれば完了できるぐらいに設計の効率化ができてきた。

go はオブジェクト指向言語ではないので generics を駆使しても、java でいうところの抽象既定クラスを用いたテンプレートパターンの実装ができない。それぞれの構造体で基本的にはコピペとなる構造体のメソッドを定義しないといけない。もちろん別のヘルパーに移譲するといったことはできるけど、状態をもっていないコードの再利用はたしかに安全ではあるけれど、状態を参照できないからそのために値をコピーするといった整合性の懸念やボイラープレート的なコードを書く必要がある。これは設計におけるトレードオフになるので go の処理系の設計に不満があるわけではない。但し、generics でできることにはまだ機能不足がある。とくに直和型の扱い。できないのかな?とググると proposal の issue がみつかるので今後の機能拡張に期待したい。

正直マネージャーが開発に工数使っていて何やってるんだとみられているかもしれない。1-2週間集中してリファクタリングしてみて、いまのアプリケーションの設計の勘所が以前よりも理解が進んだ。コードレビューだけではわからないフィードバックがある。結合テストもいくつか追加したので今後の開発で役に立つはず。これで私の (リファクタリング) 開発は終了しようと思う。

今日作ったマージリクエストの diff が次になる。

51 files +1314 -648

diff の行数だけ数えると今週だけで1万行近くは変更したと思う。