Yasu_umi’s (b)log

twitterに書くには長すぎる諸々

2022/03/05~2022/03/06 残雪期雲取山一泊鴨沢ピストン

雪山5回目、今回は初のテント泊。たまたま雪山テント泊デビューしたい方が見つかったので今回はなんとボッチではない。

YAMAPはこちら
yamap.com
同行者である天重さんのまとめはこちら。僕がバテていて全く写真のない道中の様子が詳しく纏められており多謝。
yamap.com


テント泊は2018年10月北岳以来2回目だが、あの時は自前のテントではなく同行者のテントに入れてもらったので、ちゃんとテントを担いで登るのは初となる。
てんくらはCだったが雨/雪はなさそうだったので決行となった。
初っ端から電車を間違えるガバをやらかし、バスが一本遅れてしまいスタートが30分遅くなってしまった。そして、鴨沢に着いたのが10時過ぎ。
この日の日の入りは1730頃なので、7時間ほど時間があった。全く間に合わなかったわけだが。
そんなこんなで、今回は特に初日の写真がほとんどない。時間に追われていて撮ってる余裕がなく、バテててやる気もなく、ずーっと樹林帯だったので見所もなかった。

1330頃、七ツ石小屋にて昼食を取った。
ここでは前日間に合わせて作った風防が大活躍した。


単純にお湯が沸くのが早い。風がなくても早い。
しかしまだ改良の余地は見つかった。この風防、火の調整ができなかったのだ。風防の内側は熱く、風防自体もチョー熱い。ということで次回までにはツマミ周辺を短くして取り回しを良くしようと思う。

f:id:Yasu_umi:20220309204911j:plain
15時過ぎ。ようやく七ツ石山に登頂。
f:id:Yasu_umi:20220309203641j:plain
雲取山への稜線歩きが始まる。

この辺から道の泥濘みが酷くなる。人が歩く山道は草が生えず雪が残りやすい。その雪が昼間の気温で解けてしまい泥濘み祭りだった。雪の少ない道をアイゼンを履いて歩くことの辛さを知った。

f:id:Yasu_umi:20220309205249j:plain
七ツ石山から少し下りたところで遭遇した鹿。
f:id:Yasu_umi:20220309204615j:plain
めちゃくちゃこっちを見てくる鹿。しかしこの距離で逃げないとは…
f:id:Yasu_umi:20220309205543j:plain
17時過ぎ、小雲取山にて。気温が高くて水蒸気が多くぼんやりした山々。後ろにうっすら富士山。
f:id:Yasu_umi:20220309205959j:plain
1720、雲取山山頂すぐそばの避難小屋が見える。夕陽に照らされる雪が美しい。
f:id:Yasu_umi:20220309210133j:plain
同じ場所から夕陽方向、めちゃくちゃ眩しい。時間切れの予感。
f:id:Yasu_umi:20220309210321j:plain
1730、雲取山避難小屋から振り返る。ぼんやりした空気。
f:id:Yasu_umi:20220309210615j:plain
1740、雲取山登頂。景色は翌日に託すことにしてさっさと山荘を向かう。

この後、真っ暗な中雲取山を少し下り、1820雲取山荘到着。飲食物の販売はもちろん(有り難くコーラを買った)、水場は凍っていて使えないが山荘までボトルを持参すれば水をタダ分けてもらえる、トイレもタダで使用可能など至れり尽せりだった。そして、これまた山荘からスコップを借りてテントを張るスペースを整地するところに始まり小一時間ほどグダグダ。幕営はまだまだ熟れていない。

f:id:Yasu_umi:20220309220153j:plain
結局竹ペグは購入しなかったので割り箸で代用してみた。
f:id:Yasu_umi:20220309220244j:plain
地面は凍りついているため、雪の薄い箇所では掘り返した塊を圧雪して割り箸を刺すことになった。

テントに入ればそれなりに暖かいかと思いきやそんなことは全くなく、飯の準備中もインナー手袋は外せなかった。

f:id:Yasu_umi:20220309211138j:plain
1930、ついに待ちに待った夕食の準備に入る。と言っても順番に鍋に放り込みつつ食べるだけ。

鍋が沸いてくるとテント内にも蒸気が満ちようやく手袋が外せた。しかし底冷えは相変わらずで、マット以外の部分に座るとすごい勢いでお尻が冷える。

f:id:Yasu_umi:20220309211255j:plain
こんな寒いところで食えばどんな鍋でも美味いに決まってる。
f:id:Yasu_umi:20220309211411j:plain
締めの饂飩。なぜ全粒粉にしたのか…というか多すぎてもう満腹で食べるのが結構キツかった。

結局2030頃までチマチマ鍋をつつきこの日の活動は終了。
テント内で充電できなかったモバイルバッテリーや山荘で購入し寝起きの水分にと少し残してあったコーラを体温で温めつつ、今回初導入したシュラフカバーに寝袋毎インして22時には就寝した。
この日は予報では風速15mほどとなっており、それなりにうるさかったがテントの中なら関係なし。疲労もあり爆睡できた。

f:id:Yasu_umi:20220309220359j:plain
朝食後、テントの外にザックを放り出した一枚。まだ薄暗い。
f:id:Yasu_umi:20220309211604j:plain
翌朝0530。ボチボチ荷物を片付けつつ日の出の気配。
f:id:Yasu_umi:20220309212514j:plain
さらに何だかんだしてる間にどんどん太陽が昇ってくる。
f:id:Yasu_umi:20220309212610j:plain
テント跡地。体温によってマットの跡が残った氷になっていた。
f:id:Yasu_umi:20220309212701j:plain
昨晩撮り忘れた雲取山荘。山荘に宿泊された方々も続々と出てくる。

そんなこんなで0700頃山荘出発。

f:id:Yasu_umi:20220309212837j:plain
昨日下った雲取山への道を登る。今日は寒く晴れているので見張らしへの期待が高まる。
f:id:Yasu_umi:20220309213026j:plain
0730雲取山山頂到着。圧倒的見晴し。
f:id:Yasu_umi:20220309213223j:plain
雪の富士山。陰影がくっきり見える。
f:id:Yasu_umi:20220309213418j:plain
立川まで見えているらしい。ピントが…
f:id:Yasu_umi:20220309213506j:plain
南アルプス方面。縦走が楽しそうだ。
f:id:Yasu_umi:20220309213712j:plain
昨日は泥濘るんでいた雲取山から七ツ石山方面の道も完全に凍結。めちゃくちゃ滑るがアイゼンなどがあれば昨日より遥かに歩きやすい。

この後はピークを迂回しつつ樹林帯をひたすら下山。
まず初日は気にならなかった肩凝りが発生。今までほとんど肩凝りになったことがなかったためこれには驚いた。歩くのには支障はなかったが、あのままもう一泊して歩くとなるとかなりキツいだろう。縦走で複数泊するようになるまでには対策したい。
次に足の指の痛みだ。冬山用の固い靴だったこと、雪上ではなく土の上を下ったためかなりダメージがあったこと、スキー時に履くような超極厚の靴下を履いていたため指先に遊びがなったことなどが重なり指が終始圧迫されていた。これを書いている3月9日に至ってもまだ寛解に至っていない。靴を替えるか靴下を替えるか歩き方を変えるか…いずれにせよ残雪期の低山に冬山用の靴では二度と行かないだろう。


総評
初日に7時間もかかって雲取山荘に着かなかった理由は、主に3つ
1. 荷物がやはり重い。靴も重ければアイゼンも重い。
2. 行動食サボってバテた。
3. 初日は大変気温が高く、特に七ツ石山から先が大変泥濘るんでいた。

1について、道中のほとんどはアイゼンが必要なく、必要になった場所でも軽アイゼンで十分だったのが今回の山行だった。
軽アイゼンで良くなるとコバのついた冬山登山靴を履く必要がなくなり、トータルで片足1kgは軽くできるだろう。
さらに荷物だ。今回はテント泊ということもありテントと寝袋合わせて2.5kg程度、夕食関連で1.5kg程度と日帰り山行の倍以上の重量を担いでいた。
テントや寝袋は行く場所によって増えることはあっても今以上に減ることはないだろう。となると、やはり夕食1回のために1.5kgは今の僕の体力では多すぎるということになる。
2について、毎度のことだが、疲れてる時ほどさっさと歩きたい気持ちになって食べるのをサボる癖があることを矯正するのは難しそうだ。時間を決めて取ることにしたい。
3について、これはどうしようもない。強いて対策があるとすると軽アイゼンなら付着する泥の量が減っただろうということくらいか。
いずれにせよ、まず重い荷物を担いで歩くこと自体への慣れが不足していることは間違いない。

今回は初の冬山テント泊だったが、宿泊自体は概ね快適に過ごすことができた。キャンプ地が樹林帯で風が直撃しなかったおかげというのはあるだろうが。
スコップによる整地をしないとテントを張る場所が確保できないというのは盲点だったので、次回までに買っておきたいところだ。

また、谷川岳では水を雪から調達できたが、あれは積雪期の新雪がある樹林帯より上の山ならではであり、今回のように泊地が樹林帯でしかも残雪期だと雪が汚くそのままでは飲料としては使えないこともここにメモしておく。
今回新調したサーモスの水筒は大変保温性が高く今後も重宝するだろうが、ロードバイクで使っていたボトル(谷川での反省からハイドレーションではなくボトルにしてみた)は、2日目の朝山荘で貰った水が1時間半ほどで飲み口の中で凍りついてしまった。冬山での水分を何に入れて持ち運ぶのかは次回までの課題としたい。

2022/02/19 厳冬期谷川岳天神平ピストン


去る2019年3月中旬、上野駅上越新幹線乗り場が改札から遠いのを知らずに始発に乗れないという盛大なガバから肩の小屋撤退した谷川岳にリベンジした。
雪山は、2019年3月前半の赤城山、上記谷川岳、今年2022年1月中旬の茶臼岳に続いて、今回で4回目。

ヤマレコはこちら
www.yamareco.com

前日金曜日夜にてんくらを確認するとAになっていたので準備を開始。と言っても行動食や昼食、飲み物は行きしなに調達できるので、リュックにアイゼンやゲイターなどを放り込んでさっさと寝るだけ。
2019年は赤城山の次に谷川岳に行き、まだ加減が全くわかっていなかったためかなり色んなものを持って行き疲れた反省から、今回は荷物をちょっと減らした。
具体的には、まずワカンを持って行くのをやめた。結果的に僕の歩いた位置でこれは正解だったけど、スノーシューやワカンを付けたり外したりしてる方もいた。
次に、真水を持って行かなかった。昼食はカップラにするつもりだったけど、雪からお湯を作るやつをやって実際どのくらい時間がかかるか測ってみたかったのだ。
もちろん道中の飲み物は持って行かなければならないので、0.5l水筒に蜂蜜入り紅茶、ハイドレーションに1lポカリを朝調達した。が、後述するがこれは失敗で、冬山でハイドレーションは二度と使わない。半分しか飲めなかった。
最後に、リュックを変えた。前回は60-75lのザックだったが、前回ですらスカスカだったし今回は18lのバックパックにした。
こうしてかなり軽量になったが、日帰りだからとモバイルバッテリーを忘れたのは失敗だった。地図をスマホにダウンロードしたものに頼るならバッテリーは絶対に切らせない。今回はギリギリ保ったが…
行動食は、ソイジョイを4本とセブンのレーズンバターサンド風チョコを持って行った。ソイジョイ4本は明らかに多すぎるがカップラが食えなった時のバックアップで、実際には1本しか食べなかった。チョコはジップ付きの入れ物で1粒が小さく量の調整が効き、味も良かったので今後も持って行きそうだ。

当日の時系列

上越新幹線たにがわ401号で東京06:36発上毛高原07:52着
関越交通水上線バスで上毛高原08:05発谷川岳ロープウェイ08:50着
ここで、最大手ロープウェイチケット購入列に並んで暇になった。上毛高原駅でバスロープウェイ往復セットが売っていたので、こちらで買うのが良かったのだろう。
結局09:15にようやくチケット購入してロープウェイへ。
後で確認したら、この時点でスマホのバッテリーが60%程度しかなかった。
09:30、天神平までロープウェイに運んでもらい、10分ほどかけてアイゼンなど準備開始、計画提出もポチっと済ませる。

f:id:Yasu_umi:20220221014233j:plain
スタートの谷川岳ロープウェイ天神平にて、3年前の記憶を掘り起こす…
f:id:Yasu_umi:20220221014349j:plain
最初の登りが一番しんどかった記憶が蘇えり、水平に撮ったのがこれ
f:id:Yasu_umi:20220221014524j:plain
向こうに見える山の名前はわからないけど天気が良いことはわかる
f:id:Yasu_umi:20220221014617j:plain
避難小屋直後の危ないとされてる箇所の右側
f:id:Yasu_umi:20220221014736j:plain
こっちが左側、雪庇ですね
f:id:Yasu_umi:20220221014822j:plain
この辺でようやく前が見える。すごい行列だ。スノボ担いでるBC勢がたくさんいる。
f:id:Yasu_umi:20220221015045j:plain
望遠圧縮楽しい!!
f:id:Yasu_umi:20220221015154j:plain
BC勢は大体途中で滑って降りていったようで行列が短くなった
f:id:Yasu_umi:20220221015330j:plain
良い景色だ…しかしやはり山の名前がわからない。なんなら方角もメモってない。
f:id:Yasu_umi:20220221015838j:plain
西黒尾根の方から登って来られた方々。あちらから登れるようになりたいものだ…
f:id:Yasu_umi:20220221020037j:plain
肩の小屋直前の稜線にて
写真だと風の感じがわからんなと動画も撮ってみたが一眼手持ちは厳しいことがわかった。GoProとか欲しくなってきた。
f:id:Yasu_umi:20220221020339j:plain
肩の小屋上から仙ノ倉山方面。ずっと南風なのか北側は雪が少なそう?
12:20登頂。ここでスマホのバッテリーが残り12%しかないのに気付いて慌ててGPSレコードを止める。
f:id:Yasu_umi:20220221020741j:plain
トマの耳到着。撮影会中の人が入れ替わるタイミングでサクッと撮って次へ。
f:id:Yasu_umi:20220221020906j:plain
今回は何回かBC勢の滑り出しを見たが、これが一番驚いた。
12:42にトマの耳も到着。
f:id:Yasu_umi:20220221021019j:plain
トマの耳にて。フワフワ雪が吹き付け場所は風でバキバキになるみたいだ…
f:id:Yasu_umi:20220221021209j:plain
トマの耳からオキの耳へ。約束された勝利の稜線歩きだけど、トレース外れて右に寄ると雪庇が崩れて落ちる。
f:id:Yasu_umi:20220221021335j:plain
ぼんやり気味だけど日暈が見れた
f:id:Yasu_umi:20220221021521j:plain
オキの耳にて。毎日最初に来た方が掘ってくれているのだろう。
ここで気づく、ハイドレーションからスポドリが飲めない。
なんだなんだと思って管を触っていると固い部分がある。凍ったのだ…
細い液体の入った管を氷点下で露出させているだから凍結は当然の結果である。これは猛省した。
一応凍る可能性は考えてちょいちょい飲んで水を動かそうとは思っていたが(凍らなくても水分は頻繁に取るべきだ)、トマの耳で飲むのをサボってしまいそのままオキの耳まで歩いてきてしまっていた。
幸い水筒に熱々の紅茶を持ってきていたので良かったが、こちらも象印の普通の水筒(自宅なら半日は猫舌に辛いレベルの温度を保ってくれる)だったため、ゴクゴク飲める程度の温度まで下がってきていた。次回は飲み物には注意したい。というか、普段使いの水筒で横着せずに、サーモスのステンレスボトルを買おう。
f:id:Yasu_umi:20220221021740j:plain
オキの耳から撮ったトマの耳。撮影会場から数歩後退ると雪庇が落ちてしまうことが今になってわかってしまい怖い。
ここでも動画撮影。そう、写真だと誤魔化されているがそんなに明るくないのだ。
f:id:Yasu_umi:20220221021942j:plain
下山開始。そろそろ座って休みたいなと思い始めるが、この写真の中央下部の人がいるところで休む度胸はなく、行きと同じく右側を通過。
f:id:Yasu_umi:20220221022347j:plain
肩の小屋上の看板。ガッチガチになっていた。
f:id:Yasu_umi:20220221022440j:plain
行きも風のあった稜線にて。担いで登るのは大変そうだけどかっこいい。
f:id:Yasu_umi:20220221022642j:plain
さくさく下りて天狗岩。ここでお昼を食べることに。
f:id:Yasu_umi:20220221022743j:plain
岩周辺はワイワイしてたので、斜面を掘って平地を作って雪からお湯を錬成。雪は最終的にこの4倍は突っ込んだ。
雪山では雪から水が作れるから、水を担ぐ必要がないのはやはり楽だった(2019年はめっちゃ担いで疲れた…)。今回座っての休憩はここだけだったのでちょっと長めに休み、普段は山頂でのパイプもここで。
f:id:Yasu_umi:20220221022956j:plain
下山中の仙ノ倉山方面。夕陽に照らされてるのが見たい。
f:id:Yasu_umi:20220221023358j:plain
と思って歩いてたら翳ってきた。もうちょっと待てばよかった。
15:10ロープウェイ到着。椅子やブラシが用意されており至れり尽せりだった。立ち止まっていると寒いので下りてきてしまったが、この日のロープウェイの最終は16:30だとチケット購入時に教えていただいていたので、上でもっとゆっくり写真を撮ってくれば良かったと若干後悔した。
f:id:Yasu_umi:20220221023451j:plain
無事下山できてほっとしつつアイゼン脱いだりしてる間に日が向こう側に行く。
f:id:Yasu_umi:20220221023600j:plain
ロープウェイに乗る前に振り返った山頂。また来たくなる。
谷川岳ベースプラザにてバッテリーを購入したりコーラを飲んだりしつつバスを待つ。
関越交通水上線バスで16:10谷川岳ロープウェイ発16:58上毛高原着。
上越新幹線たにがわ414号で上毛高原17:27発東京18:40着。

総評

バッテリーに飲料と準備段階での反省が多い。
山行自体は天気もまあまあで快適だった。長蛇の列には驚き、後ろを歩いていた慣れていそうなボードのお兄さんに聞いたところ、"間違いなく今年一番だし例年もここまで人がいることはなかなかない"とのことだった。
登りのペースが遅かったこともありそうだが、後日の筋肉痛もなく、歩いてる途中も疲れが少なかったのかアイゼンをどこかに引っかけたりもしなかったのは良かった。
水分摂取や行動食、体温管理や歩き方などはやはり回数を重ねると向上するものなようで、そういった様々な要因で楽になってきているのかもしれない。
とはいえ、ラッセルするような体力は全くないので、もっと登るなら真面目に定期的な運動が必要そうだ。
そういえば、そろそろピッケルを持って歩くというのをやってみようと思っていたが、急だったので調達が間に合わず、今回は代わりにストック不使用縛りで歩いてみた(代わりになってるのか謎だけど…)。
次回どこかに行くまでには調達して、制動の練習も始めてみようと思う。

2021年の話

3月: 引っ越し

3月に一軒家に引っ越した。時期の問題もあり数が少なく大変探しやすく、予想していたよりはアッサリ引越し先を決めることができた(時期の問題で数も少なかったのだろう)。 庭があるのが大変良いのだが持て余している感があり、今年はほぼ何もできなかった。 2022年はもう少し何かを植えたり使ったりしていきたいところだ。

f:id:Yasu_umi:20211231181950j:plain
雨後の縁側にて

5月: 大型二輪免許取得

取っただけである。教習は2020年10月頃から通っていたが、電話での予約が面倒になりどんどんズレこみ2021年5月にようやく取得できた。 今年は免許を取った後は特にバイクに乗ることもなく、人に免許を取った話をするたびに「で、バイクは何買ったの?」と聞かれ答えに窮するばかりであった。 2022年はレンタルして旅行に行きたいものだ。

6月: 歯医者に通うようになれなかった

何かを噛んで虫歯になっていた歯を割った。痛みで全て忘れたが多分虫歯になっていたことにも気づいておらず、歯を割った後もしばらく放置し、当然のように根腐れし最終的には痛みで呻くだけの生き物になるところまで放置した。この時ばかりは自分の怠惰を相当恨んだ。 割れた歯を抜くついでに親不知も抜いてもらったりしたが、ここでもやはり予約が面倒になり通うのを途中でやめてしまった。 2022年は歯医者通いを再開しなければならないだろう…

7月: 沖縄 -> 福江島 -> 長崎 -> 熊本 -> 阿蘇、車で旅行する

身内の結婚式で沖縄に行ったついでに長めの旅行をした。

f:id:Yasu_umi:20211231183138j:plain
昼の宮古島海岸にて
宮古島は上記のように良い天気だったがこの後台風がやって来たことで、当初計画していた石垣や本島の旅程が全て吹っ飛んでしまった。 そこでせっかくなので車の運転の練習をしようと思いたち、福江や長崎の教会を巡りつつ、阿蘇まで行ってカルデラを眺めようというかなり忙しい旅程を組んだ。 余りにも急だったため手落ちも多く、特に30分の差で雲仙ロープウェイは痛恨だったが、幸い天気には恵まれた旅行になった。
f:id:Yasu_umi:20211231183940j:plain
阿蘇スカイラインにて

8月: 礼文島を歩く

イッタラー数人と礼文島旅行をした。何故礼文島に行こうということになったのかは忘れたが、おそらくTLに6月頃の高山植物の綺麗な写真が流れてきたからだろう。8月に行っても見られるはずもないのに… 高山植物のシーズンは外していたが、大変暖かく(というか暑かった。まさか30度を越えてくるとは…)全く人のいない島の西部を縦断するルートを歩くという僕の目的は達成され、ギリギリ季節だった塩水漬けの雲丹を食べることもできた。次回は利尻に行きたいところである。

f:id:Yasu_umi:20211231184818j:plain
礼文島8時間コースにて

9月: ついにアメコミに手を出す

昔からわかっていたから避けてきた、手を出すと歯止めは効かず大変なことになるに決まっていると… 何故"まあ買っても良いか"となったのかはもう定かではないが、おそらくエターナルズの原作を読みたい->だったらもういっそ読めるだけ読んでしまえ、というような流れだろう。 買った冊数がわからないが(amazonで数えようと思ったが50を越えた辺りですぐ面倒になった)、少なくとも本棚が1つ埋まる程度の量にはなっている。 重点的に邦訳されている、マーベル世界におけるAvengers DisassembledからAVX辺りまでは特に楽しく読むことができるし、絶版で話が抜けるようなところもほぼなかったので、マーベル世界観が好きでコミックを読み始められる僕のような人間にはこの辺りからが良さそうだった。邦訳版を買う際は可能であれば中古を避け、中古を買う際には注釈のペーパーが付いているか確認できるところから買うことをオススメする。 アメコミは出版されている量も膨大であり、邦訳がされているもののどうしても人気のあるキャラに偏ってしまうようで、世界で起こっていることイベントの全体感を掴むことが非常に難しい。 今年はAmazingSpider-ManEternalskindleでポチって読んでいたが、2022年はFFなどに手を出していきたい。

12月頭: BBQに行く(ついにグリルも買っちゃう)

恒例化したいと思っているBBQキャンプ、3回目が開催できた。今回は群馬の上毛高原キャンプグランドに行った。 回数を重ねてきたことで手際がだいぶ改善され、十分な調理時間を確保できたことで肉もうまく焼けるようになってきた。 2022年は、可能なら春になる前の寒いうちにもう一度開催したいが…

f:id:Yasu_umi:20211231191841j:plain
説明不要の肉

12月末: 長野を好きになる

スタッドレスを買った後輩に運転してもらって、帰省の道すがら蓼科を旅行した。ピラタス蓼科はコースバリエーションが少ないが、八ヶ岳ロープウェイのおかげで高い位置にあるスキー場なので雪はかなり良かった。スノーシューレンタルがあったので、ハイキングをしたがこれは大変良く、また雪山登山に行きたいという気持ちが強まる旅行になった。

f:id:Yasu_umi:20211231191226j:plain
北横岳坪庭にて
また、今回宿泊した蓼科親湯温泉は、部屋がアップグレードされた下駄を考慮してもなお大変素晴しい宿で、夏にもし登山に来ることがあればまた泊まりたい宿だった。

総括

引越しをしたことにより生活が大きく変わったはずで、ここに書いていない忘れてしまったことも沢山あったように思う。twitterは1年を振り返るには雑多すぎるし、そもそも結構サボってる時期があるためもう何もわからない。記憶は大事だが記録も大事だとようやく思えるようになってきたのは良い変化だろうか?

幾何処理するサービスをリファクタする時にやって良かったことリスト

そろそろwebエンジニアを名乗るしかなくなってしまったyasu_umiです。

この記事は、AEC and Related Tech Advent Calendar 2021に合わせて書かれたものであり、実験的に実装されたコード山盛りの状態でそのままプロダクトの一部として実働を初めてしまったサービスをPONと渡された方向けになります。

まずは表題を回収しておきます。

やって良かったことリスト

  1. 手元の実行環境を整える
  2. E2E*1なテストを用意する
  3. lint, testをCIで回すようにして、lintを通す
  4. E2Eのテストが壊れないように設計を見直す
  5. ユニットテストを追加する

ここまで読んだ多くの人が、なーんだそんな話か(そっ閉じ…となっていることを切に願います。

まずは状況の確認から

それでは本題に入りましょう。

"実験的に実装されたコード山盛りの状態でそのままプロダクトの一部として実働を初めてしまったサービス"とは一体どのような状態か、深刻度順に行くと…

  • テストがない
  • linterがない
  • CIしてない
  • 手元の実行環境が本番と合ってるか怪しい

この辺りを満たすものとします。この状態のサービスにおける機能追加は

  • コードを書く
  • 何となく手元で動かしてみる
  • 以前と結果が違ったり違わなかったりするのを目視で確認する
  • リリースしてみる

というサイクルになるでしょう。この状態でサービス開発に人を追加投入するのは非常に困難です。新しく開発に関わる人は、以前と結果が違ったり違わなかったりするのを目視で確認することができません。*2

ということで、新しく開発に関わる人こと僕は、幸いにもこのサービスは開発が活発なわけではなく、しばらくの間であれば開発を完全に止められるという状況もあり、大規模なリファクタに着手することになりました。

リファクタするぞ!の前に(実行環境編)

それではリファクタを…始めてはいけません! 事故る!!

この状態のコードに大幅な変更を加えるのは自殺行為です。そもそも、リファクタが必要なのは結果が変わった時に確認する方法がないからです。この状態での大規模なリファクタは間違いなくバグを埋め込んでしまい、大量のコード変更のどこでバグったのかすらわからなくなるでしょう。

そこでまずは手元の実行環境を整えましょう。

PaaSを使っているにせよ何にせよ、手元で本番環境と同じ環境で動作確認できるようにしましょう。具体的にはdockerを使って言語やランタイムを揃え、パッケージ管理システムを使ってライブラリのバージョンを固定します。

注意点は、ここでパッケージを更新したり言語のバージョンを上げたりしない、ということです。まずは動いているものに全ての状況を揃えましょう。

リファクタするぞ!の前に(E2Eテスト編)

じゃあ準備もできたしリファクタを…始められない! バグる!!

動作環境は整ったものの、結果の動作結果の確認は変わらず目視です。幾何処理するサービスにありがちな問題ですが、結果が複雑すぎてテストを書くのが大変難しいかもしれません。そんな時に有効なのは雑なE2Eテストです。

サービスインしているということは、ユーザからの入力と出力があるはずです。もしかしたら過去にバグった事例があり、その時の修正確認に使った入力が残っていれば最高です。どんな形であれ、まずは今のサービスからの出力を取りましょう。

これを使ってE2Eテストを書くことで、最低限既存と同じような動作をすることを保証しつつ、中を弄ることが可能になります。

注意点は、ここでユニットテストを書かない、ということです。この段階ではコードの中身はまだしっちゃかめっちゃかでこの後大規模にリファクタするため、この時点で頑張ってコードを読み解きユニットテストを書いても、ほぼ無駄になってしまうでしょう。

リファクタするぞ!の前に(linter&CI編)

じゃあE2Eテストで安全にリファクタできるようになったし、今度こそリファクタするぞ!

でも良いのですが、もう1ステップだけ間に挟みましょう。linterです。

コードの書き方は様々です。同じ機能を実装するにしても様々な書き方があり、現状では何の縛りもなくあらゆる書き方のコードが許されてしまいます。これでは個々の書き手の趣味全開のコードがガンガン入ってしまい書き方が安定せず、読む時に大変です。そこでlinterを入れ、書き方に一定の制約を加えられるようにしましょう。

また、linterは手元で実行できるだけでは誰も守りません。github actionsなどでpull reqマージ前に必ずlinterを通し、通っていないコードはmergeできないようにしましょう。その2で追加したE2Eテストも必ず通してからmergeされるようにしましょう。

リファクタするぞ!

ついにリファクタです。まずは設計から、といきたいところですが、中身のわからないコードの設計を見直すのは大変です。オススメは長い関数の分割をしつつコード読み、です。ある程度関数分割が進んだら、次はクラスの見直しです。この段階まで来たら、コードの中身もある程度理解でき、ユニットテストを書き始められるでしょう。

リファクタの先に

"リファクタは盆栽"*3とは良く言ったもので、リファクタに終わりはありません。CIでのチェックなどを入れたとはいえ、破壊することは非常に容易です。特にチームで開発している場合、リファクタ直後のコードの状態を維持していくにはチームの協力が不可欠なので、linterの内容はテストカバレッジの確認などなど、チームとしてどうしていくか相談しましょう。

余談

具体的な話を削ったところ、当たり前かつ幾何処理とほぼ関係ない話になってしまいました。

それでも、幾何処理するサービス*4特有の問題を上げるとすると、

一点目は、ロジックの変更に伴なうE2Eテストの結果変更の是非判断が非常に難しいことです。例えばPolylineのSegment分割ロジックを変更したとすると、結果のPolylineの座標が増減したり変化したりしますが、これをgithub上でreviewすることは困難です。つまりE2Eテストはあくまで命綱であり、開発サイクルの中でこれを頼りにすることはできません。

二点目は、ユニットテストを書くことは容易いということです。処理の分割さえ適切に行えば、そして、サービスが複雑な内部状態を持たなければ*5という2つの但し書きはつきますが。

ということで、これらの作業はコードが少ない方が簡単です。コードを書き始める時にやっておけば、大規模なリファクタなんてやらなくても、コスト0で綺麗なプロジェクトがスタートできますし、コードの内容を覚えているうちならユニットテストを書くのはさらに簡単です。

オマケ

さて、ここからは具体の話です。この話は2つのプロジェクトにおいて起こった問題を纏めたものであり、1つはnodejs, もう1つはpythonのプロジェクトです。

まずは1つ目のnodejs。package.jsonから関係のあるところを抜粋すると

{
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^4.33.0",
    "@typescript-eslint/parser": "^4.30.0",
    "eslint": "^7.31.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-import": "^2.23.4",
    "eslint-plugin-jest": "^24.4.0",
    "eslint-plugin-prettier": "^3.4.0",
    "eslint-plugin-promise": "^5.1.0",
    "jest": "^27.0.6",
    "pprof": "^3.2.0",
    "prettier": "^2.3.2",
    "ts-jest": "^27.0.5",
    "ts-node": "^10.2.1",
    "typescript": "^4.4.2"
  },
  "eslintConfig": {
    "env": {
      "node": true,
      "es2020": true
    },
    "extends": [
      "eslint:recommended",
      "plugin:@typescript-eslint/recommended",
      "plugin:promise/recommended",
      "plugin:prettier/recommended",
      "plugin:jest/recommended",
      "plugin:jest/style",
      "prettier"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
      "ecmaVersion": 11,
      "sourceType": "module"
    },
    "plugins": [
      "@typescript-eslint",
      "promise",
      "import",
      "jest"
    ],
    "rules": {
      "@typescript-eslint/naming-convention": [
        "error"
      ],
      "@typescript-eslint/explicit-module-boundary-types": "off",
      "no-use-before-define": [
        0
      ],
      "import/order": [
        "error",
        {
          "newlines-between": "always",
          "alphabetize": {
            "order": "asc"
          }
        }
      ]
    }
  },
  "prettier": {
    "tabWidth": 2,
    "singleQuote": true,
    "trailingComma": "all"
  },
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "node",
    "coveragePathIgnorePatterns": [
      "/node_modules/"
    ]
  }
}

こんな感じ。受け取った時点では生jsで書かれていたので、まずはtypescriptを入れてjestでE2Eのテストを書いて…という流れでした。

そもそもの問題として実行が非常に遅いアプリケーションであるのをどうにかしたい、というのが本題だったので、pprofはそのために使いました。この記事にあるような内容を終えた後には、ts-nodeでframe graphを見るなど。見え方は先達の記事を参考まで。

次にpython。こちらはpipenvを導入しかけた形跡があるものの、実態としてはrequirements.txtを使っており…全部やめてpoetryにしたのでpyproject.tomlから関係のあるところを抜粋すると

[tool.poetry.dev-dependencies]
pycln = "^0.0.4"
isort = "^5.9.2"
autopep8 = "^1.5.7"
add-trailing-comma = "^2.1.0"
black = "^21.7b0"
flake8 = "^4.0.1"
flake8-commas = "^2.1.0"
flake8-comprehensions = "^3.7.0"
flake8-quotes = "^3.3.1"
mypy = "^0.910"
[tool.isort]
profile = "black"
line_length = 120
multi_line_output = 3
include_trailing_comma = true
overwrite_in_place = true

こんな感じ。pythonのlinter, formatterはnodejsと比較すると群雄割拠状態であり、まだこれが最強、という組み合わせを見つけられずにいます。

これはどちらのサービスにも当て嵌ったことですが、幾何処理系のライブラリは技術が枯れているのかメンテされていないライブラリがメジャーに使われているのを見受けました。 2年以上更新がなかったら諦めてforkしてしまい、自力メンテを視野に入れてよいでしょう。もちろんpull reqを出してmergeしてもらうのが一番ですが。 また、このようにメンテされていなくて良くわからないライブラリを使ってしまっている場合、影響範囲を局所化することは何よりも大事です。 古いライブラリになるほど、nodejsであればtypescriptの型定義が、pythonであればmypyの型定義がないものです。これをあちこちで使ってしまうと別のものに変えることすら一苦労です。 型定義をサクッと書ければそれが一番良いですが、それが難しそうな場合、用途に合わせて入出力の型注釈をちゃんと書いてテストもあるモジュール内に閉じ込めてしまえば、同様に動作するモジュールに置き替えることで、後日依存を外すことが容易でしょう。

最後に

具体的な話と抽象的な話どちらに需要があるカレンダーなのか迷った結果、結局どちらも書いてしまいました。 10を100にするようなサービス開発をされている方には"こんな当たり前のことを今更"という内容であり、0を1にするような実験的なコードを書かれている方からは"こんな面倒なことやってられるか"という内容でしょう。 1を10にするタイミングで*6、チラとでも思い出していただければ幸甚です。

*1:end-to-end。インテグレーションテストの方が一般的かな。

*2:長らく開発してると忘れがち…

*3:出典を探したが特定できなかった。ご存知の方は是非ご一報を。

*4:今更ですが、本記事における幾何処理サービスは、(CADファイルのような)複雑な入力を受け、加工し、複雑な出力を返すものとします。

*5:バックエンドにDBがあって複雑な状態を持ってたりしていない

*6:もし100までこの状態で行ってしまったら…また別の対処法になるでしょう。全部書き直すとか。