へっぽこびんぼう野郎のnewbie日記

けろけーろ(´・ω・`)! #vZkt8fc6J

JavaやPHPのハッカソンに潜り込んでPythonにリプレイスしてきた話

先日忍び込んだハッカソンで、「Java+Spring Frameworkで書かれたこのサンプルコードか、PHP+Laravelで書かれたこのサンプルコードを参考に何かつくってください」というお題をもらったので、朝から夕方まで7時間弱で食事は水オンリーで作業した結果、Python+Djangoでリプレイス作業をしたという成果をだして、気づいたことがあったのでとりあえずブログに書きます。成果はGitHubにあがってますが、汚いので見ないでほしいです。あとそのままでは動きません。pipで必要そうなライブラリをインストールしてください。

書く前の知識

  • Javaは書いたことある
  • Tomcatは知っている
  • SpringBoot自体は実行したことは1回ある
  • PHPは知らない
  • Spring Framework何も知らない
  • pom.xmlとはなんなのか
  • LTIやCaliperというIMC Globalという団体が作ってる規格がよくわからない(そもそもこれがメイン)

得られた知識

Java関係のもの

  • pom.xmlは、NPMでいうpackage.jsonみたいなもん
  • Spring Frameworkの中にはTomcatがあるものもある
  • DI使われていて便利そうだった。ただServiceの使われ方が何かおかしいように感じた
  • Javaはやっぱり冗長だった。Modelのところにgetなんたらとかが大量にあって悲しく感じた
  • RepositoryでのCrudRepositoryは便利そうな感じがしたけど、Djangoの文化と違いすぎてやるならフレームワークを作るところからやる必要を感じた
  • Spring Bootでのエントリーポイントが、PythonでいうWSGIみたいな感じで萌えポイントだった
  • SpringのControllerで、アノテーションにRequestMappingがあるのはいいなと思った(いまさら感)
  • IntelliJは良い

PHP関係のもの

  • 引き続き知らない

JavaPHP関係以外で得られた知識

  • LTIは要はHTTPのセッションに何入れるかのやつ。それとOAuth2
  • CaliperはCaliper(日本人はみんなそんなに知らないということがわかった)

パワーコードの教訓として得られたもの

パワーコードは実践では良くない

Spring Frameworkのことを全然知らないままやったので、何がどうなっているかわからないまま書き始めていた。

それで「とりあえず」と最初に決めたことにあとあとまで引きずられてしまった。たとえば最初はHello Worldからやったんだけど、そのHello Worldの書き方を最後まで継承し、コピペでいとも簡単に増殖した。

わからない→わかった!→じゃあこの書き方まずいよね→まぁいいか

というスパイラルが繰り返された結果、負債につぐ負債が誕生した。

たとえばViewを書くときに、最初はHello Worldからやったので、View(DjangoのView。Spring FrameworkではController)はクラスから作らず、関数で書いていた。↓こんなの

def index(request):
    return HttpResponse("Hello World")

ここにロジックを追加(個人的にロジックはここに集約すべきではないと思うけど)して、あとからテンプレートを追加することになったので、とりあえずテンプレートにコンテキストを渡せるものを作成することにした。

このへんで、サンプルのJavaコードで使われている「model」には、Djangoで使われているコンテキストの役割もあるということが明確に判明した。

それと同時に、ThymeleafというSpring Frameworkの中で使われるテンプレートがあることを知った。書き方はunderscoreやJinja2とは違って、どっちかって言うとWPFxamlみたいな感じ。

def my_render(template_file, modellike):
    template = Template('<html>foo</html>')
    html = template.render(Context({}))
    return html

def index(request):
    # ~~~ ロジック ~~~
    return my_render("index.html", model)

この状態から、ファイルからロードしてレンダリングする場合に、テンプレートの文字列をいちいちロードするのがめんどくさいので、ファイルをその場でオープンするという荒業を使っていた。

def my_render(template_file, modellike):
    source = open('app/templates' + template_file).read()
    template = Template(source)
    html = template.render(Context(modellike))
    return html

def index(request):
    # ~~~ ロジック ~~~
    return my_render("index.html", model)

そして、ここのindex関数部分がコピペでどんどん増えていく。

最初からDjangoで用意されていたものを使えばいいという話だけど、そもそもわかってない状態から作り始めてこの時点で「こういうふうに書くのがSpring Web MVC Frameworkというやつらしい」ということを理解したので、最初からそういうふうに作ることは知識的に無理だった。

なので、こんな感じでどんどん最初のものから負債が溜まっていく。0.1の瑕疵が最終的に全てを食い尽くすモンスターになっていった。

そのため、初期ではわずかしか行わなかったデバッグ作業も、終盤では多く含まれていた。つらい。

他人が書いたいいところを活かせず、自分の領域にすべて持ち込んでしまう

Spring FrameworkにはどうやらDIがあるというのを知ったけど、これを時間内に実装するのは無理だと判断して無視して実装していった結果、Pythonの方で冗長になり、何の生産性もないクラスがいくつかできあがってしまった。

逆に明確にDIを理解していないことが判明したことはよかった。

いわゆる抽象的な概念の知識が増えない

時間に追われていることもあって、既知の知識の範囲内でコーディングすることしかできなかった。

新しい難しい概念で腰を落ち着かせて考えるべきものは、こういうコードの書き方では手に入れられないなと思った。

今回得られた知識は、最初からある程度知っていた知識を焼き直したぐらいのものだった。

そういう面から見ると、業務とは直接関係ない勉強というのは重要だと思った。

時間管理は重要だった

「これは絶対無理。これはいける。これはできるけど時間的にキツいし、なくてもいいから切り捨て」という判断がだいじだった。

「この領域は何分でできるか。1個で5分なら、5個で完成だから15分くらいかな?」「残タスクはどれだけかマークダウンで洗い出し」というように見積もって、時計をときおり見ながらタスクをこなしていった。

タスクの洗い出しは、各領域を整頓するために脳からシリアライズしたぐらいで、途中で完全に全体構造が見えてからは全く必要なり、バージョン14時ぐらいで更新停止した。

ものすごく疲れる

タスクが完了してから、空腹と頭痛と体のだるさを一気に感じて死にそうだった。

帰りの電車で座れなくて「アアアアア」とゾンビみたいになっていた。

たぶんいつもやってる仕事よりやや簡単だったのでゾーンに入っていたのだと思っている。

時間管理の結果「飯を食う時間は無駄」と判断して休憩なしでやっていたので、こういう作業の仕方ばっかりやってると体を壊しそう。

ライブラリを読むのは正義

他人が書いたライブラリの中でエラーが発生したときは、まず読み、意味がわからなかったらググり、エラーの意味がわかるけどなんで出ているのかわからない場合、ライブラリの中でデバッグするという挑戦が必要だと思った。

それで仕様がわかったりしてた。(ちなみにハッカソンではあと5分しかないのに、なんでエラーになるのかわからないところがあって、最終的にライブラリの中身を編集して完成させるという暴挙に出た)

推論重要

「こうなってる?→なってたらここはこうなって、こっちはこうなってるはず→なってた!!!」というプロセスを繰り返せていて、特に何もドキュメントを読むことなくSpring Frameworkの外観を把握できた。なので今ドキュメントを読むと相当いろんなことがわかるようになっていると思っている。

新しいことを学ぶときは他人が書いたコードが一番勉強になる

ドキュメントを読むより、人が時間かけて書いてくれたコードをガンガン読んだ方がドキュメントより正確だし、自分で思考して推論していけるのでよかったと思う。

たぶんドキュメントを読んで自分でSpring Frameworkの環境を構築してJavaで書いていくより、効率的に学習できた。

ただし、既知の概念だけでは得るのが難しいような概念(自分にとって完全に馴染みのない概念)は、おそらくそもそも推論の原理的に難しくて、未知の情報に対して既知の情報が足りなさすぎて、解決への探索に計算リソースが膨大に必要になると思った。

だから、情報量が格段に多い、いわゆる難しい概念を日常的に獲得していくことで、他分野の知識も得られやすいのかなと感じた。

英語できないとつらすぎ

ハッカソンの最初で、アメリカ人の人が最近の動向とかを説明してくれていた。1時間ぐらい。最初は日本人向けに英語をゆっくり話していたけど、慣れてきたのかだんだん早くなっていき、そもそもよくわかっていないものをよくわからない単語と組み合わされて話されていたので、一部理解できても、ものすごくつらかった。

ふつうに英語の重要性を感じた。

おもったこと

最近本業でわけわかんないのにインフラ周りばかりやっていてスランプぎみだったので、集中してコーディングできて、一つのものを作り上げることができたのは、精神衛生的に良かった。

なので、こういう本業とは直接関係ない、やつあたりみたいなコーディングも時々やるのがいいかなと思った。

Spring Frameworkがどうこう以上に(たぶん個人的には使わないし)、色々と教訓が得られたのがよかった。

振り返ってみると全然大したことしてないので、たとえばDIのへんとか勉強したいと思った。

🐹

「この問題は本当に問題です」に触発されて問題作った

人に「この問題は本当に問題です」の問題が「これおもしろい。こういうの解きたい」って言われたので「じゃあ作ってあげる」ということでつくった。

いちごを5つ入れられる袋が8つあります。
その袋のうち半分にいちごを入れられるだけ入れ、
残りの袋には3つずつ入れます。
こうして作った袋をすべて入れたダンボールを1箱として、
AさんはBさんに1箱300円で6箱売りました。
Bさんが渡された箱をすべてあけてみると、
中のいちごの半分が腐っていました。
いちごの価値を1つ10円とすると、
Bさんはいくら損をしたでしょう?

けっこうめんどくさいはず

交通費のスクレイピングとかだるいのでAlfredのWorkflowとシェルスクリプトで調べるようにしてみた

はじめに

仕事中に気まぐれに読んでた記事があったので、すぐできそうだしあったら便利だなと思って、アイディアを丸パクリして実装してみました(今も仕事中)

ascii.jp

前提条件

  • Alfredがインストールされていること
  • Alfredが課金版であること

使い方

Ctrl + Space で Alfredというアプリを呼び出したあと、 eki 東京 名古屋 のようにスペースを開けて検索すると値段を表示してくれます。

f:id:haruharu1:20180807120409p:plain

f:id:haruharu1:20180807120403p:plain

作ったやつの置き場所

2018-08-07_atb6d37d | Files.fm.

これをAlfredワークフローに追加すればいけると思います(てきとう)

ここからダウンロードするの怖い人は自分で作ってください

おまけ

ワークフロー

f:id:haruharu1:20180807120759p:plain

スクリプトの中身はこんなかんじです。

query=$1

q1=$(echo "$query" | cut -d ' ' -f1)
q2=$(echo "$query" | cut -d ' ' -f2)

open "https://www.jorudan.co.jp/norikae/cgi/nori.cgi?Sok=%E6%B1%BA+%E5%AE%9A&eki1=$q1&eok1=R-&eki2=$q2&eok2=R-&eki3=&eok3=&eki4=&eok4=&eki5=&eok5=&eki6=&eok6=&rf=nr&pg=0&Dym=201808&Ddd=7&Dhh=11&Dmn=37&Cway=0&C1=0&C2=0&C3=0&C4=0&C6=2&Cmap1=&Cfp=1&Czu=2"

リファレンス

Splitting a query into two variables - Discussion & Help - Alfred App Community Forum

Jmeterを使ったDDoS的な負荷試験のやりかた

jmeter-serverを使って、jmeterクライアントから「これで動かして〜〜」という旨を、いろんなクライアントに送り、最終的にDDoS的な攻撃ができる。

注意

jmeter4.0だとリモートで動かなったりする。RMIのNotSerializableExceptionとかが出る(´・ω・`) なぜかは不明。

3.3をインストールするとあっけなく使えるので3.3推奨。

jmeterそのものの使い方は知っている前提で書くので、普通のjmeterの使い方の範疇に入るような、自明なことは特に説明しない。

また、通読にはインフラエンジニアの初歩的な知識を要求する。

概要

コンピュータをたくさん使う。

構成は、Master/Slave構成というか、1クライアント+複数サーバという構成

Jmeterクライアントから、複数のJmeterサーバに「こういう感じで試験やってよ」という指令を送り、Jmeterサーバは、ターゲット犠牲者に対してリクエストを送る

Jmeterサーバ、Jmeterクライアントでそれぞれ設定が必要。けっこうセンシティブなので、雑なコンフィグだとすぐにエラーをはいてくれる。

サーバとクライアントが一体化しているので、jmeterをインストールしたらサーバ機能もクライアント機能も入っている。

クライアントとサーバの通信はRMIとかいうJavaのリモートプロシージャコール(RPC)的なのを使う。そのためのポートは自由に指定できる。ちなみにぼくはRPCをよくわかってない。誰か教えてください。

この人の説明がわかりやすい(英語) ↓

https://www.artofsoftwaredevelopment.com/performance/performance-testing-in-the-cloud-with-jmeter-aws

↑の記事と、この記事で疑問点はほぼ解消されると思う(たぶん)

やることの順番

・インスタンスを複数用意(クライアント用1つとサーバ用複数)
・それぞれに同じバージョンのjmeterをインストール。Javaも同じ必要がある(らしい。別に違ってもいけるときとかあるし)
・jmeterサーバの設定をする(全サーバ同じでいい)
・jmeterクライアントの設定をする
・jmeterサーバの起動
・jmeterクライアントから負荷試験の指令を送る

いきなり100台たてて全部設定とかしてるとサーバ費用で死ねるので、まずはローカルで試して、おkそうならサーバを2台立てて試し、その後3台にして、そのあとで100台でやるとかの方がいいと思われる。

jmeterのインストール

ここから3.3をもってくる。4.0はカッコいいけど動かない。普通に動かす分にはよさげ。インストールは、zipとかtgzを展開するだけ。

https://archive.apache.org/dist/jmeter/binaries/

インスタンスjavajmeterをインストール

java8とかjava9がいいらしいぞ

↓ Ubuntu16.4だとこんなん

How to Install Oracle Java 8 / 9 in Ubuntu 16.04, Linux Mint 18

設定

ここにする。サーバ側の設定もクライアント側の設定もここでするので、間違えないようにする。

$ vim apache-jmeter-3.3/bin/jmeter.properties

サーバ側の設定

ポート番号に大した理由はない。自由に設定していいけど、この番号で設定していることを前提に話を進めるので変えたい人はそこに注意。デフォルトは1099だけど使いたくない。

server_port=24001
server.rmi.localport=26001

クライアント側で結果を受け取るための設定をする

設定しないとデータを受け取るためのポート番号がランダムに変わる(なんだその仕様・・・)

こちらもポート番号に大した理由はない。自由に設定し(ry

client.rmi.localport=25000

ポートについて

AWSでやる場合は、これらのポートをもとにしたセキュリティグループをつけたりして、上記のポート番号を許可しないと途中でとまる(あたりまえ)

本当はセキュリティグループは22番だけにして、SSHトンネリングをしたほうがセキュアなのでいい(めんどくさいけど)

SSHトンネリングはするとhostnameで判定するため、127.0.0.1とか10.0.255.238とかlocalhostとかいろいろややこしくてめんどくさくなる。特に複数あるとSSHトンネリングだけでしんどい。

ぶっちゃけ一瞬ポート開けたぐらい大丈夫だし、しかも仮に乗っ取られてもすぐインスタンス停止するから大丈夫だろとか思ったりとかもしない。

でもセキュアにしたい場合はSSHトンネリングしたり、めんどかったらAWSのセキュリティグループでプライベートIP指定したりする。後者の方が楽。

jmeterサーバの起動

これを複数のサーバで実行する。別に ./jmeter -s でもいいけど、どっちでもいい。

$ ./jmeter-server

SSHトンネリングとかするために、ホスト名を入れる場合はこんなかんじで。

$ ./jmeter-server -Djava.rmi.server.hostname=localhost

jmeterクライアントから負荷試験の指令を送る

こんなかんじ。jmxファイルは、jmeterで保存したテストプランのファイル。詳細はぐぐれ。

-R を使うと、通信先を指定できる。カンマで区切って複数指定する。

$ ./jmeter -n -t 【GUIのJmeterで保存したテストプランのファイル(拡張子は.jmx)】 -l 【出力ファイル(csv形式で出力される)】 -R 10.0.255.238:24001,10.0.255.237:24001

オプションの意味に関しては $ ./jmeter -\? で見れるので、そちらを参考に。

コマンドラインで複数指定したくない場合は、クライアントの jmeter.properties

remote_hosts=10.0.255.238:24001,10.0.255.237:24001

のように書いて、

./jmeter -n -t 【GUIのJmeterで保存したテストプランのファイル(拡張子は.jmx)】 -r -l 【出力ファイル(csv形式で出力される)】

でよい。

SSHトンネリングなどしてホスト名がある場合はやはり -Djava.rmi.server.hostname=localhost をつける。

これで終わり。

リファレンス

keystoreの設定方法

Apache JMeter - User's Manual: Remote (Distributed) Testing

keystoreのパスワードがおかしいとか言われた

java - keytool error Keystore was tampered with, or password was incorrect - Stack Overflow

SSHトンネルする

How to run jmeter over ssh tunnel | ionel's codelog

やり方について

Performance Testing in the Cloud with JMeter & AWS

marshalexceptionについて

(ロシア語) azure - Jmeter MarshalException: аргументы сортировки ошибок - Qaru

azure - Jmeter MarshalException: error marshalling arguments - Stack Overflow

サーバがビジーって言われたら

Killすればおk

distributed - JMeter: JMeter remote test fails with error as "Engine is busy, Please try later" - Stack Overflow

SSHトンネリングに使いそうなスクリプト

# sh tunneling.sh [localport] [target-ip]
second_localport=$(($1 + 2000))
echo "$1 port is used and target ip is $2"
ssh -L $1:localhost:24001 -R 25000:localhost:25000 -L $second_localport:localhost:26001 -N ubuntu@$2 -i ~/.ssh/jmeter-stress-test.pem -f

まとめ

これでインスタンスの数だけリクエストを送りまくれる。

50万リクエスト/秒とか飛ばして人のサーバをぶっつぶせるので楽しい!タノシイ!タノシイ!

誰かのクレカさえ奪えれば気軽にDDoSできる世の中すごい怖い。

ちなみに攻撃ツールとしては指令が失敗した場合止まるのであんまり役に立たないと思います。

たのしい負荷試験ライフを!

免責

免責があります。

HTMLでエスケープするときに使う&quot;とか&#34;とかについて

「HTML特殊文字」とか「HTMLエスケープシーケンス」とか「HTMLのエスケープ文字」とか言うと混乱する場合があるのでちゃんとした名前を使う。

たとえばパーサ書いてレンダリングしてるときに、こっちのエスケープにあっちのエスケープしてこっちのエスケープがとかやってると、何のエスケープかわからなくなりバグ修正のときに困る。

それで、なんか長年ずっとたたずんでいるこうした「HTML特殊文字」に、パッと調べた感じビシッとした感じの日本語訳ないので人と情報が共有できない。

なのでまとめてみた。

公式名称はw3.orgから引用したよ。

Using character escapes in markup and CSS

数字のやつ【&#34;や&#x34;】

10進数は

decimal numeric character reference

16進数の方は

hexadecimal numeric character reference

俗称:

Numeric Character Entity
Numeric Code Character Entity
Numeric Code Character
Numeric Character Reference
NCR

Numeric character reference - Wikipedia

名前のやつ【&quot;とか&amp;とか】

named character reference

俗称:

Character Entity
Character Entity Reference
NCR
HTML entity 

参考:

Character entity references in HTML 4

ぼくの使い分け方

対象 よびかた
とくに使い分けないとき NCR
10進数の 10進数のNCR
16進数の 16進数のNCR
数字の Numeric Character Reference / 数字の方のNCR
名前のやつ Named Character Reference / 名前の方のNCR
NCRの表のこと NCRs

 

ひろめていきたい(`・ω・´)ゞ

おまけ

HTMLではエスケープはNCRを使いましょう(1敗)

<div id="1" attribute="a\"b">だめ</div>
<div id="2" attribute="a&quot;b">おk</div>

<script>
var divs = document.querySelectorAll('div')
divs.forEach(e => {
  console.log('ID', e.getAttribute('id'))
  console.log('attribute', e.getAttribute('attribute'))
})
</script>
ID 1
attribute a\
ID 2
attribute a"b

その他

もうちょっと詳細書いたのがあるので、興味がある方はこちらへどうぞ

ㇹ゚ン゚'ㇳ̃ヴ゙ニ゙コ゚ヮヰ文̂字̠コ゚−ト゚ノ゙ㇵナ゚ㇱ(現在に至るまでの文字コードの軌跡と簡単な使い方について) - へっぽこびんぼう野郎のnewbie日記

ぼくが何を考えてコードの修正をやるのかをとりあえず適当に書いてみたよ

他の人のブログを読んで、自分の中で思っていたことを整理したくなったので書いてみました。

パラグラフなど考えずに、思いついたものを文章の流れの中に差し込んでいるだけなので、箇条書きやTip集みたいになっていて、コンテキストが頻繁に変わるので読みにくくなっていると思います。あらかじめご了承くださいm( )m 骨子は概要のとおりです。

概要

何が問題かを把握する
→ 原因がどこかを特定する
→ どう解決するのか策を練る
→ 実際に解決する
→ 同じ原因で起こっている別の箇所にある問題を特定する

1. 何が問題かを把握する

「何が問題か」を、文で正確に表現できるまで切り分ける。

画面Xで、ユーザーAが、条件Cで、ボタンTを押したときに、Pが発生するべきであるが、なぜか、ときどきQが発生してしまう

他人が絡む場合は、ここで確認を取る。自分の勘違いで違う場所を試していたり、仕様だったりすることもある。バグ報告が実態と違っていて再現できないこともある。

確認ができたら問題を分割していく。文のうち、どこの箇所が削除可能かを考えて、これ以上削れないというところまで試していく。

分割は超重要。考えるべきことが大きく減るので絶対にやるべき。この分割の部分は、複雑になればなるほど、集合論の知識が役に立つ。

確認も重要。確認不足のせいで「あとあと考えると、そもそも問題ではなかったこと」のための無意味な実装をする場合もある。問題を正確に把握することはかなり重要

「なぜそれが問題なのか」も共有しなければ、実際は潜在的な問題が別の箇所にあるにも関わらず、この問題だけを解決するための次のフェイズに進むことになる。

問題把握が正確な場合、この段階でそもそもコードを1行も書かずにみんなが幸せに終わることも多い。

よくよく聞いてみると運用次第で実現できるとか、予算や工数的に確実に赤字になる無謀なチャレンジになるとか、そもそも顧客の勘違いだったとか、簡単にできればやってほしいだけで、キツそうならやらなくていい案件だったとか、バグじゃなくて仕様だったとか。

なので、炎上の予防としてもかなり有用。

コードをごにょるのだけが解決策ではないというのを念頭に置く。

分割は↓みたいな感じでやる。

ユーザーAが、条件Cで、ボタンTを押したときに、Pが発生するべきであるが、なぜか、ときどきQが発生してしまう

 ↓

どのユーザーでも、条件Cで、ボタンTを押したときに、Pが発生するべきであるが、なぜか、ときどきQが発生してしまう

 ↓

条件がCでない場合で、ボタンTを押したときは、全くQが発生しない

 ↓

どのユーザーでも、条件Cで、ボタンTを押したときに、Pが発生するべきであるが、なぜか、ときどきQが発生してしまう

ここまで問題を分割することができた。分割できてはじめて、原因を特定し始める。

2. 原因がどこかを特定する

「原因は絶対これだ!」と早とちりしない。

ときどき「これかもしれない!!!」とピンと来るときもあるが、そういうときも、いきなり解決策を書くのではなく、冷静に「なぜ原因がこれであるのか」という証明をする。

そうしないと、気のせいとか夢を見ていたとか、見ていた画面が開発画面じゃなかったとか、おバカな理由で、治ったことになってしまう場合がある。

ピンときたあとは「解決のときが近い!」と感じがちだけど、そのまま突き進むとドツボにはまる。おとなしくピンと来る前の気持ちに戻って原因を特定する作業を続行する。

何もわからない砂漠のような場所でウロウロしていると、ちょっと緑色のなにかが見えただけで「オアシスだ!!」と感じてしまい、そして「さっきここに緑色の何かがあったからきっとここがオアシスだ!」と考えて、オアシスではない場所で水を探しつづける羽目になる。実際にはずっと砂漠なのに。

これかもしれない!!!!!あるぇ?違ったぞ……じゃあこれか!!!!?違う??なんで!!これか??!!!じゃあこっちは!!!?これかああああ!違うの!?ナンデ!?これかあぁぁああああ?アアアアアアアアー

という手段でもたどり着くときはあるし、こっちのほうがたどり着くのが早いこともある。ただしたまにどれだけ時間をかけても一歩も進んでいないことがある。これをハマるという。個人的にハマるべくしてハマったハマりと考えている。

原因を特定するためには、コードを探索することが肝で、幻影や誘惑に惑わされず、「ここまでは正しい」ことを確認し続けて探索したり、ひたすらコードを追う作業を行う。

「これがこうなっているはずだから確認は不要」というような考えは捨てて、ひとつひとつバカみたいに確認して、範囲を狭めていく。結局ここでも問題を分割していく。

これは明らかにこうなっているはず。実行するまでもないね。まぁとりあえず実行してみるか……ほら、こうなって……なってねぇえええええじゃああああん

ということが死ぬほど多い。自分も他人も信じず、ただ機械だけを信じることが必要。

問題が判明するまでは、「解決しよう」という気持ちは完全に捨てて、まず「いったいどこで問題が起こっているのか」を探る。解決したい気持ちが先行すると、これを見落とすことが多い。

調べ終わると、以下のような文章が書けることが多い。

「条件 f=True」のときかつ「ボタンTのonClickイベントが発火した」とき、内部のクロージャが呼ばれて、クロージャ内部にある実装の甘い非同期処理によってときどき canGenerateData=False となり、そのため後続の処理によってQが発生する

 ↓

「非同期処理の実装が甘かった」理由は、複雑な非同期処理を行うための変数が足りず、変数を外部から持ってこようとすると、コードの複雑度が上がるため。平たく言えば、場当たり的な非同期処理のため。

ライブラリの中も追うと原因が特定しやすくなってべんり。勉強にもなる。

知識が不足していると、実際には原因でないものを「これが原因だ」と思い込んで、次のフェイズに進んでしまうことがあるので「自分の知らない知識がある」ことを前提にことを進める。自分の知識の範囲の中だけで考えないようにする。

3. どう解決するのか策を練る

原因を特定できてすぐに解決策が見つかることもあるが、そうでないことの方が多い。

「コードの構造がおかしいのでこれを変更しなければいけない」「変数名が混乱を来した主原因だったので変数名を変更する必要がある」などなど。

「そもそも仕様がやばいこと」もある。「このままやっても理論的に不可能なこと」もある。そうなると実際の解決までめちゃくちゃ遠い。場当たり的な解決になることも稀ではない。

解決策を考えるフェイズでは、今度は考える範囲を狭めるのではなく、増やしていく。いろんなコードを見る。コードの外も見る。マネージャサイドや営業サイドに解決策が眠っていることもある。

そもそも非同期処理である必要がなかったので非同期処理をやめた。非同期処理である必要がない理由は、単に非同期処理やってみたかっただけだったから。

影響範囲もちゃんと考える。修正はなるべく小さくする。小さくできない場合は、かなり大きな問題が眠っている。

影響範囲を考えないと、「この問題」を解決した代償として別の問題が何個も発生するといったことが起きる。そうなるともう、もぐらたたきみたいな感じ。

非同期処理のスコープはかなり小さく、処理の遅延も無いため同期処理にしても問題は発生しない。発生しないことも確認した。

原因を特定するのは簡単でも、解決策を探るのはとてもむずかしい。「こっちを立てればあっちが立たず」のようなことが多い。

「バグの直接的な原因」は簡単でも、「バグが出るコードが書かれてしまった原因」を解決するのは根本的な部分なので、極めて難しいし見えにくい。解決方法も調べて出てくるものではない。

ここはもう広範囲に勉強して、実践して、発見できるようにするしかないと思う。

4. 実際に解決する

ここまでのやり方が全く見当違いなこともある。

実際に解決する段階で、アクシデントがあまりにも多い場合、手順にミスがあることが多い。まだまだ原因を特定する必要がある。

時間や人的資源の不足や、知識の欠如によって「本当は、真に解決はできていないけど、今はこうしなければならなかった」で終わることも多々ある。

これは「負けた」と考えるべきで、「まぁいいか」と考えてしまうと、「まぁいいか」が積み重なって「ひどいプログラム」が完成する。

5. 同じ原因で起こっている別の箇所にある問題を特定する

同じ轍は踏まない。

だいたい発生した原因と似たようなコードが別の場所にある。コピペだったり色々。

だって今まで「そう書くのはダメだ」って気付いてなかったんだから、他の箇所もそうなってるに決まってる。

これは原因から再現させられるので、別のバグを比較的すぐに発見できる。ここまでの作業よりは遥かに容易い仕事だ。

そのバグを直すかどうかはまた別の話だが、どれだけあるかを洗い出しておく。でないと後で忘れるため。

また、この原因を訓戒とする。

今後はコードレビューで予防する。これで少なくとも技術的負債の利息は増えなくなる。

おわりに

まとめると

問題見つける → 原因見つける → 解決策見つける → 解決する → 教訓にする

というだけの1行で「なぁんだ。当たり前のことじゃないか」として終わることだけど、その1つ1つがどれも重いタスクなので、1つ1つに真摯に向き合って、いつも心に秘めておいて、自分に問いかける必要がある。

だいたいそんなふうに考えるとうまくいくことが多い。

ただ、原因を見つけるのがつらすぎたり、解決策が浮かびにくいと、どうしても悪い方向の誘惑に負けてしまう。

深追いするための知識が足りてないと「あーつらい!こことかここを試したらなんかうまくいかないものか……頼むぅ頼むぅ直ってくれ〜〜〜🙏」と悪魔に祈りがちになる。

落ち着いて「急がば回れ」の精神で、楽そうに見える遠回りな道を選んでたどり着けるかどうかを試すのではなく、絶対にたどり着くであろう茨の道を辛抱してゆっくり着実に道を作りながら進む方が最終的に早く解決する。

ただし短期的には解決が遅くなる場合が多いので、ついつい楽そうな道を選んで何個も "解決" したくなることがある。そこに気をつける。真の茨の道は、一見楽そうに見える道なのだ。

逆に、現実的な問題として、いつでも茨の道を選ぶことが正解というわけでもない。ここに難しさがある。ただ個人的にはぼくは茨の道を選んでいきたい。「昨日の敵は今日の友」とも言うし、茨の道も次の日には高速道路になる。

解決方法の模索や原因調査の段階で「膨大な基礎知識が要求されること」は多い。知らないことは考えられない。たとえばプロファイラという言葉を知らなければプロファイラは動かせないしプロファイラ使おうという発想も浮かばない。

そういうとき、あらかじめの勉強や実践がものを言う。なので「知識はないけどこの考え方さえあれば大丈夫さ」というわけでは全然ない。知識がないとわけわからないことが多すぎてひたすらつらすぎるだけだ。そうして悪魔の誘惑に負けてハマる。

また、知識不足のせいで次の一手をどう取ればいいのかわからなくなったとき、ぼくは詰んだと表現することが多い。ハマりはしないけど詰む。そして新たな問題が噴出する。ここに来るとコードの問題ではなかったりする。

なぜ詰んだのかは、詰んだ場所からは自明でないことが多い。そして「なぜここで自分は詰んでいるのか」という問いを得る。小問題のはじまりだ。そしてタイトルに再帰する。

そんな感じで考えています。

指定したディレクトリ下にある全ファイルの拡張子を見るコマンド

コマンド

$ find <Directories you will search> -type f | sed 's/.*\.\(.*\)$/\1/g' | sort | uniq

$ find */templates -type f | sed 's/.*\.\(.*\)$/\1/g' | sort | uniq
css
html
js
txt

もちろんファイルの個数を数えることもできるぞい!

 find */templates -type f | sed 's/.*\.\(.*\)$/\1/g' | sort | uniq -c
   1 css
 239 html
   8 js
   6 txt