9巻まで読みました。 ライトノベルファンタジー。魔法は出てきません。ただ主人公が天才という理由で政治、外交、戦争で無双するお話。「敵に裏を読まれたら、主人公は裏の裏まで読んでいた!」な爽快感が味わえます。安心して読めます。
ジャンルは違いますがACMA:GAMEと似ている感じがしました。
アニメ化するみたいです。 tensaiouji-anime.com
9巻まで読みました。 ライトノベルファンタジー。魔法は出てきません。ただ主人公が天才という理由で政治、外交、戦争で無双するお話。「敵に裏を読まれたら、主人公は裏の裏まで読んでいた!」な爽快感が味わえます。安心して読めます。
ジャンルは違いますがACMA:GAMEと似ている感じがしました。
アニメ化するみたいです。 tensaiouji-anime.com
~ bundle exec rake db:create Deprecation warning: Expected string default value for '--quiet'; got false (boolean). This will be rejected in the future unless you explicitly pass the options `check_default_type: false` or call `allow_incompatible_default_type!` in your code You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION. Deprecation warning: Expected string default value for '--syslog'; got false (boolean). This will be rejected in the future unless you explicitly pass the options `check_default_type: false` or call `allow_incompatible_default_type!` in your code You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION. Deprecation warning: Expected string default value for '--logfile'; got true (boolean). This will be rejected in the future unless you explicitly pass the options `check_default_type: false` or call `allow_incompatible_default_type!` in your code You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION. String can't be coerced into Integer /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activesupport-3.2.22.2/lib/active_support/core_ext/enumerable.rb:60:in `+' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activesupport-3.2.22.2/lib/active_support/core_ext/enumerable.rb:60:in `sum' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activesupport-3.2.22.2/lib/active_support/core_ext/enumerable.rb:60:in `sum' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/connection_adapters/postgresql_adapter.rb:747:in `create_database' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:144:in `rescue in create_database' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:84:in `create_database' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:62:in `block (3 levels) in <top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:62:in `each' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:62:in `block (2 levels) in <top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:240:in `block in execute' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:235:in `each' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:235:in `execute' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:179:in `block in invoke_with_call_chain' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/2.5.0/monitor.rb:235:in `mon_synchronize' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:172:in `invoke_with_call_chain' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:165:in `invoke' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:150:in `invoke_task' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:106:in `block (2 levels) in top_level' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:106:in `each' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:106:in `block in top_level' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:115:in `run_with_threads' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:100:in `top_level' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:78:in `block in run' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:176:in `standard_exception_handling' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:75:in `run' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/bin/rake:33:in `<top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/bin/rake:23:in `load' /Users/shigerunakajima/.rbenv/versions/2.5.7/bin/rake:23:in `<top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:74:in `load' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:74:in `kernel_load' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:28:in `run' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli.rb:463:in `exec' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli.rb:27:in `dispatch' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli.rb:18:in `start' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/exe/bundle:30:in `block in <top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/friendly_errors.rb:124:in `with_friendly_errors' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/exe/bundle:22:in `<top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/bin/bundle:23:in `load' /Users/shigerunakajima/.rbenv/versions/2.5.7/bin/bundle:23:in `<main>' Couldn't create database for {"adapter"=>"postgresql", "encoding"=>"unicode", "database"=>"pubannotation", "pool"=>5, "host"=>"localhost", "username"=>"postgres", "password"=>"password"} String can't be coerced into Integer /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activesupport-3.2.22.2/lib/active_support/core_ext/enumerable.rb:60:in `+' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activesupport-3.2.22.2/lib/active_support/core_ext/enumerable.rb:60:in `sum' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activesupport-3.2.22.2/lib/active_support/core_ext/enumerable.rb:60:in `sum' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/connection_adapters/postgresql_adapter.rb:747:in `create_database' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:144:in `rescue in create_database' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:84:in `create_database' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:62:in `block (3 levels) in <top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:62:in `each' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/activerecord-3.2.22.2/lib/active_record/railties/databases.rake:62:in `block (2 levels) in <top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:240:in `block in execute' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:235:in `each' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:235:in `execute' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:179:in `block in invoke_with_call_chain' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/2.5.0/monitor.rb:235:in `mon_synchronize' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:172:in `invoke_with_call_chain' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/task.rb:165:in `invoke' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:150:in `invoke_task' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:106:in `block (2 levels) in top_level' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:106:in `each' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:106:in `block in top_level' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:115:in `run_with_threads' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:100:in `top_level' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:78:in `block in run' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:176:in `standard_exception_handling' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/lib/rake/application.rb:75:in `run' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/rake-10.5.0/bin/rake:33:in `<top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/bin/rake:23:in `load' /Users/shigerunakajima/.rbenv/versions/2.5.7/bin/rake:23:in `<top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:74:in `load' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:74:in `kernel_load' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:28:in `run' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli.rb:463:in `exec' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli.rb:27:in `dispatch' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/cli.rb:18:in `start' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/exe/bundle:30:in `block in <top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/lib/bundler/friendly_errors.rb:124:in `with_friendly_errors' /Users/shigerunakajima/.rbenv/versions/2.5.7/lib/ruby/gems/2.5.0/gems/bundler-1.17.3/exe/bundle:22:in `<top (required)>' /Users/shigerunakajima/.rbenv/versions/2.5.7/bin/bundle:23:in `load' /Users/shigerunakajima/.rbenv/versions/2.5.7/bin/bundle:23:in `<main>' Couldn't create database for {"adapter"=>"postgresql", "encoding"=>"unicode", "database"=>"pubannotation_test", "pool"=>5, "host"=>"localhost", "username"=>"postgres", "password"=>"password"}
エラーが起きているのは https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/core_ext/enumerable.rb#L58-L64 です。
def sum(identity = 0, &block) if block_given? map(&block).sum(identity) else inject(:+) || identity end end
Ruby 2.4で Enumerable#sum
が実装されたことが原因のようです。
60行目で呼ばれるsum
が ActiveSupportのEnumerable#sum
からRubyのEnumerable#sum
に変わるようです。
なぜそうなるのかはよくわかりません。
Ruby 2.5でのみこのエラーが起きることを確認しました。 おそらくRuby2.4でも起きると思います。
rake db:create
以外は正常に動くので、psqlでPostgreSQLにログインして、次のSQLを実行してdatabaseを作成しました。
CREATE DATABASE "pubannotation" ENCODING = 'unicode'; CREATE DATABASE "pubannotation_test" ENCODING = 'unicode';
CPANに上がってるモジュール、一つ一つの粒度が小さいから読みやすいし、ドキュメントもテストもしっかり揃ってて挙動を把握しやすくて、自分にとっては最高の教科書だった
OSSで公開されているソースコードは、最高の教科書ですよね。 Perlにはなじみがないので、卑近な例としてJavaScriptの場合を考えてみます。
HTML文字列を生成する際に、ユーザー入力をそのまま出力するとタグが生成されてHTMLが壊れることがあります。 というかXSSの脆弱性です。そこでHTMLエスケープをしたいなと思うのですが・・・どう実装したら漏れなく対応できるかわからない状況を仮定しましょう。
JavaScriptではnpm | build amazing thingsというパッケージリポジトリが主流です。 まずはここで検索します。
HTMLエスケープしたいので、雑にhtml
とescape
で検索してみましょう。
https://www.npmjs.com/search?q=html%20escape
224パッケージも出てきて、どれを見たらいいか迷います。
まずは一番うえのhtml-escaper
から見てみましょう。
https://www.npmjs.com/package/html-escaper
まずダウンロード数を確認します。 700万とか異次元の数値が出ています。 広く使われているようです。
参考までに2番目のxss
パッケージも見てみましょう。
90万件と十分に使われています。 が、html-escaperは一桁多いので、HTMLエスケープ用のパッケージとしては、html-escaperがもっとも広く使われていると推測できます。
つぎにhtml-escaperの実装を見てみましょう。
右側のHomepage
の欄にGitHubへのリンクがあります。
https://github.com/WebReflection/html-escaper
JavaScriptのライブラリはindex.js
がエントリポイントになっていることが多いので、inedx.jsを見てみましょう。
一番下までスクロールすると、次のようにexports
にescape
とunescape
を代入している箇所が見つかります。
JavaScriptのCommonJSというパッケージでは、exports
オブジェクトを使って、定義した関数を公開します。
このescape
の実装が見つかれば、HTMLエスケープの実装がわかりそうです。
少し上にスクロールすると、次のように41行目でescape
が定義されている場所が見つかりました。
次の定義からescape
はreplace関数を呼んでいることがわかります。
const escape = es => replace.call(es, ca, pe)
このreplaceは何でしょうか?
24行目でconst {replace} = '';
と定義されています。
これはつまりString.prototype.replaceです。
分割代入を使って、組み込み関数への参照を取得する方法を初めて見ました。驚きです。勉強になりますね。
replace
をcall
で呼び出しているので、es.replace(ca, pe)
と同じです。
es => replace.call(es, ca, pe)
とアロー関数で定義されているので、次の関数定義と同じです。
function espace(es) { return es.replace(ca, pe) }
第一引数ca
は何でしょうか?28行目で定義されていいます。
const ca = /[&<>'"]/g;
第二引数pe
は何でしょうか?30〜37行目を見てみましょう。
const esca = { '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }; const pe = m => esca[m];
置き換えたい文字列をキーにして、置き換え後の文字列を返す関数です。 String.prototype.replaceは第二引数に関数を指定できます。 MDNでは次のように説明されています。
新しい部分文字列を生成するために実行される関数で、regexp や substr でマッチしたものを置き換えるのに使われます。この関数に渡される引数は下記の「引数としての関数の指定」で述べられています。
つまり、escape
関数は引数で与えられた文字列で、esca
マップに定義された文字の組み合わせで置き換えることがわかりました。
&
, <
,>
, "
, '
を置換できれば、十分有効にHTMLエスケープとして動きそうだと推測できます。
やりました。 ここまでで、HTMLエスケープの仕様を調べずに、HTMLエスケープの仕様をなんとなく把握しました。
いい感じにOSSのライブラリから実装を見つけられないときは、 大体「HTMLエスケープの実装が知りたい」みたいないい感じのゴールを設定できていない時なんですよね・・・。
テーマはプログラミング速度が速い分野を増やすです。
2020年は、特定のプロジェクトでのプログラミング速度の向上に成功しました。
とはいえ、今年のオープンソース活動振り返り @ 2020 | Web Scratch と見比べると大分遅く感じます。 また、2020の成功例は、プロジェクトや技術ドメインへの習熟度、プロジェクトへの関わり方に依存しているように思います。 そこで、他のプロジェクトでのプログラミング速度向上を目標とします。
サブ目標
2020年のテーマは「使う道具を増やす以外の方法で技術力を上げる」でした。
2019年のふりかえりと2020の目標 - @ledsun blogによると、当初は次のような作戦を立てていました。
理解しているレイヤーを増やすようなアプローチが必要かな?と思っています。 「作って理解するOS」を読み始めました。まだ途中です。 2020年には、実装するところまでやりきりたいです。 2017年に「RubyでつくるRuby 」を読んだときは、考え方を応用してできることがぐっと増えたので、同じような効果を期待しています。
半年ほどたって、「論理的思考の放棄」をパクる - @ledsun blogで
1日1000行のリファクタリングなら、できる気がする。
と感じて、作戦変更しました。 プログラミングそのものの速度を追求するアプローチです。
ふたたび Gitのdiffを振り返る - @ledsun blogによれば、どうやら成功したようです。 概ね次のような策を実施しました
それぞれ次のような効果がありました。
アプローチを変更してから、明らかな効果が出るまで半年掛かりました。 半年程度頑張るとプログラミング速度は目に見えて上げられるようです。
必要があって、2週間ほどSVGの勉強をしました。 2週間の間は、結構な時間をとって集中しましたが、成果は芳しくありませんでした。
SVGの概念の把握や周辺ツールの理解は進みました。 しかし、SVGで絵が描けません。 またSVGを操作したり、生成したりするプログラミングを書いたとして、それが仕事になるイメージができません。 要するに、新しい技術ドメインに取り組む場合、サイドプロジェクトで取り組んでも全然時間が足りないことがわかりました。
冷静に考えれば、2週間で仕事になるレベルで技術が身につかないのは、当たり前です。 ですが、知らない技術ドメインは解像度が低すぎて、不当に難易度を高くまたは低く見積もってしまいます。 メインプロジェクトとして取り組む時間が確保できれば、もう少し解像度が高くなるのでしょう。 ただ、その時点ではメインプロジェクトになる予定が延期されました。
メインプロジェクトで、技術力を上げられるアプローチを採用しました。
ふたたび Gitのdiffを振り返る - @ledsun blog で日単位で集計しました。
週、月、年単位の集計結果も見ていきましょう。
gnuplot> set xdata time gnuplot> set timefmt "%Y-%m-%d_%H:%M:%S" gnuplot> plot 'all_log_week.dat' using 1:2 w i title 'insertions' lw 2, 'all_log_week.dat' using 1:3 w i title 'deletions' lw 2
グラフが見やすくてよいです。
gnuplot> plot 'all_log_month.dat' using 1:2 w i title 'insertions' lw 2, 'all_log_month.dat' using 1:3 w p title 'deletions'
これも全体的な傾向をつかめているように思います。
今後の追跡調査は、週または月で集計するのが良さそうです。
gnuplot> plot 'all_log_year.dat' using 1:2 w l title 'insertions', 'all_log_year.dat' using 1:3 w l title 'deletions'
2019年が5月開始なので、8ヶ月分のデータしかありません。 期間が1.5倍なので、増加量としては妥当な感じです。
insertions/deletionsの比率が1.1から1.3に増えているのが興味深いです。 機能追加が多かったのでしょうか?
続々 Gitのdiffを振り返る - @ledsun blogでコミット単位でのファイルの変更行数の遷移をふりかえりました。
コミット単位では列間が詰まりすぎてグラフが見にくいです。 特に、Gitのコミット数を振り返る - @ledsun blog で見たように、2020年9月以降コミット粒度を小さくしています。
このため列間がますます詰まり、グラフから情報が読み取りにくくなっています。 そこで、ファイルの変更行数を、再び、日、週、月、年の単位で集計します。
require "Time" require "Date" duration = $*.shift.to_s.to_sym Commit = Struct.new(:at, :insertions, :deletions) # カウントしたくないファイルを除外 EXCLUDE = "':(exclude)package-lock.json' ':(exclude)*.min.js' ':(exclude)*.css' ':(exclude)dist/*' ':(exclude)dev/vender' ':(exclude)src/lib/modules' ':(exclude)*.md'" `git log --after='2019-05-22 00:00' --format=format:'---%n%ai' --shortstat #{EXCLUDE}` .split("---\n")[1..] # 1行目は空なので捨てる .map { _1.split("\n") } .filter { |time, stats| !stats.empty? } # 除外しているファイルがあるのでstatsが出ないことがある .map { |time, stats| Commit.new(Time.parse(time), stats.split(",")[1].to_i, stats.split(",")[2].to_i) } # " 1 file changed, 10 insertions(+), 9 deletions(-)" をパース .filter { _1.insertions < 1000 } # 1000行以上の変更はツールによるので除外する .sort_by { _1.at.to_i } .reduce({}) do |result, item| case duration when :day date = Date.parse item.at.to_s # 日 when :week date = Date.parse(item.at.to_s) - item.at.wday # 週 when :month date = Date.parse item.at.strftime("%Y-%m-01") # 月 when :year date = Date.parse item.at.strftime("%Y-01-01") # 年 は期間が違い過ぎるので、比較には向かない else date = item.at end if result.key? date result[date].insertions += item.insertions result[date].deletions += item.deletions else result[date] = Commit.new(date, item.insertions, item.deletions) end result end .each_value do puts "#{_1.at.strftime("%F_%T")} #{_1.insertions} #{_1.deletions}" end
引数で、day
、week
、month
、year
を受け取り、それぞれの単位で集計します。
出力データ形式は続々 Gitのdiffを振り返る - @ledsun blogと一緒です。
gnuplot> set xdata time gnuplot> set timefmt "%Y-%m-%d_%H:%M:%S" gnuplot> plot 'all_log_day.dat' using 1:2 w i title 'insertions' lw 2, 'all_log_day.dat' using 1:3 w i title 'deletions' lw 2
続 Gitのdiffを振り返る - @ledsun blogのグラフと比べると大分ちがいます。 変更行数の変動が少なく、現実の作業効率を反映していそうです。
どうやら、本当に一日に2000〜2500行の変更をしているようです。
今年は 「論理的思考の放棄」をパクる - @ledsun blog に書いたように 1日1000行のリファクタリング を目指してプログラミングスタイルを工夫していました。半年を経て、どうやら達成したようです。 現時点では、一回しか達成していないので、再現性がないかもしれません。 来年は、これがフロックでないことを確認したいと思います。
「1日1万行とか、絶対無理じゃん」という思いもありましたが、あと4倍なら、俄然現実味が帯びて参りました。
続 Gitのdiffを振り返る - @ledsun blog で1000行以上の変更があるコミットはツールによるフォーマット変更であることがわかりました。
また、日付でまとめてしまうと一日の変更行数が2000を超えます。
これはgit rev-list
のbeforeやafterの挙動によるものです。
必ずしもコミットした日にまとまっていません。
例えば、つぎとコミットがあったとします。
バグ修正を先にpushしたかったので、rebaseして、コミットBをコミットAの前に移動します。 するとコミットAは1/15日以降のコミットにカウントされます。 このように実際の作業日とはずれて集計されます。 特にリリース直前の日に作業が固まりやすい傾向があります。
そこで期間中の全コミットをもってきて、そのままグラフ化します。
require "Time" Commit = Struct.new(:at, :insertions, :deletions) # カウントしたくないファイルを除外 EXCLUDE = "':(exclude)package-lock.json' ':(exclude)*.min.js' ':(exclude)*.css' ':(exclude)dist/*' ':(exclude)dev/vender' ':(exclude)src/lib/modules' ':(exclude)*.md'" `git log --after='2019-05-22 00:00' --format=format:'---%n%ai' --shortstat #{EXCLUDE}` .split("---\n")[1..] # 1行目は空なので捨てる .map { _1.split("\n") } .filter { |time, stats| !stats.empty? } # 除外しているファイルがあるのでstatsが出ないことがある .map { |time, stats| Commit.new(Time.parse(time), stats.split(",")[1].to_i, stats.split(",")[2].to_i) } # " 1 file changed, 10 insertions(+), 9 deletions(-)" をパース .filter { _1.insertions < 1000 } # 1000行以上の変更はツールによるので除外する .sort_by { _1.at.to_i } .each { puts "#{_1.at.strftime("%F_%T")} #{_1.insertions} #{_1.deletions}" }
次のような行が3000行続きます。
2020-12-25_17:22:45 4 4 2020-12-25_17:24:41 10 9 2020-12-25_17:27:11 9 3
結果をファイルにいれてgnuplotで表示します。
gnuplot> set xdata time gnuplot> set timefmt "%Y-%m-%d_%H:%M:%S" gnuplot> plot 'all_log.dat' using 1:2 with line title 'insertions', 'all_log.dat' using 1:3 with line title 'deletions'
2019年と2020年で、プログラミングのピーク性能は特段変わっていないことがわかりました。
Search result of “user:ledsun created:>2020-01-01” - Qiita
意外とたくさん書いていました。10記事書いて19LGTM。とても平和です。
2020年に作ったGitHubリポジトリ - pockestrapにインスパイアされました。
次の条件でGithubを検索します。
Search · is:public user:ledsun created:2020 · GitHub
大体どれも作りかけです。
SVGおじさんに憧れて素振りしていた時のものです。素材をSVGにする部分が難しいなあ、素振りしてもなかなか身につかないなあ・・・というところで止まりました。
Github Actionsの練習用
ブラウザで音声を保存します。構想では永続化しようと思っていました。getUserMediaで取得したデータをWaveフォーマットにエンコードするところまで書いて止まりました。
WebGLの勉強がてらthree.jsを素振りしていた時のものです。three.jsがいい感じに抽象化してくれているおかげで、WebGLの概念が身につかないなあ・・・というところで止まりました。
Rubyプログラミングの練習問題を管理するアプリケーションです。 問題文はMarkdownで書けます。 問題の解答をPostするとサーバサイドで実行して正解か不正かを判定する機能があります。 テストデータをどうやって書こうかな?と悩んだあたりで止まりました。
2021年はなにか形があるものを残したいですね。
2020年のプルリクエストを振り返る - kakakakakku blogを参考にしました。 あまりにも少なかったのでissueも入れました。 次の条件でGithubを検索します。
is:public author:ledsun created:2020
Search · is:public author:ledsun created:2020 · GitHub
2番目のIssueが解決済みなことを思い出したので、閉じておきました。ふりかえりは大事ですね。
Homebrewをインストールし直す - @ledsun blogでrbenvをgitから入れました。
git pullします。
~ cd .rbenv/ ~ git pull --ff-only Already up to date.
んー、rbenv install --list-all
しても3.0.0が出てきません。
~ cd ~/.rbenv/plugins/ruby-build ~ git pull --ff-only
が、必要でした。
~ ruby --version ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin18]
Gitのdiffを振り返る - @ledsun blog でGitの記録から、約2年間の毎日のファイルの変更行数を見ました。 1日に4000行の人力では不可能に思える変更が見つかりました。 今度は巨大な変更を含むコミットを探してみましょう。
require 'date' EXCLUDE = "':(exclude)package-lock.json' ':(exclude)*.min.js' ':(exclude)*.css' ':(exclude)dist/*' ':(exclude)dev/vender' ':(exclude)src/lib/modules' ':(exclude)*.md'" start_date = Date.parse $*.shift end_date = Date.parse $*.shift limit = $*.shift.to_i git_rev_list_command = "git rev-list --after='#{start_date} 00:00' --before='#{end_date} 23:59' current" p git_rev_list_command revs = `#{git_rev_list_command}`.split("\n") revs.reverse.each do |revision| result_of_git_show = `git show --shortstat --oneline #{revision} #{EXCLUDE}` short_stat = result_of_git_show.split("\n")[1] next unless short_stat insertions, deletions = short_stat.split(",")[1..].map { _1.to_i} next unless insertions next unless deletions if insertions > limit || deletions > limit system "git show #{revision} --format=format:'%H%n%ai%n%s' --shortstat #{EXCLUDE}" puts "\n" end end
実行してみると、次の結果が得られました。 1000行以上の変更をしているコミットを探しました。
~ ruby gitdiffindate.rb 2019/05/22 2020/12/31 1000 "git rev-list --after='2019-05-22 00:00' --before='2020-12-31 23:59' current" 663c84dbf80a105b22078418f87c2474ac9487a5 2019-07-24 14:58:23 +0900 code-style: Introduce the Prettier code formatter 330 files changed, 3990 insertions(+), 1883 deletions(-) 5c3f2c2122f676df8f91a03df5ef12830082cb93 2019-07-24 15:01:49 +0900 code-style: Ban one-var 56 files changed, 1077 insertions(+), 1086 deletions(-) cf2acd9f917e2652048df97050c0e885aaec0731 2019-11-12 11:18:55 +0900 refactor: Format annotation files 10 files changed, 2543 insertions(+), 2158 deletions(-) d5c2b8881c9edbececee8b7753a8f04b2dd3f5cd 2019-11-19 15:35:29 +0900 refactor: Format less files 6 files changed, 1181 insertions(+), 968 deletions(-) 5bb834fca322388da6b3ca94db99e4910f6f774a 2019-11-07 20:28:24 +0900 feature: Show attribute tabs in the Pallet 25 files changed, 1180 insertions(+), 69 deletions(-)
発見したコミットログを見ると、ほとんどがツールを使ったフォーマット変更でした。
feature: Show attribute tabs in the Palletは、機能追加です。 機能追加とはいえ、1コミットで1000行入れるのは、我ながらよくなかったと反省です。
この5つのコミットが2019-07-29
と2019-12-02
に集計された結果、1日4000行オーバーになっていました。
つまりノイズです。これらの異常値を除いてグラフを作り直してみます。
今度は2000行近い変更が異常値に思えてきました。
Gitのコミット数を振り返る - @ledsun blogで、Gitのコミット数をふりかえりました。 今年はコミットの粒度を変えたためコミット数が増えています。 その結果、作業効率は上がったのでしょうか?今度はGitのdiffをふりかえってみます。
日付単位でgit diffのsohrtstatをとります。
3 files changed, 8 insertions(+), 3 deletions(-)
こんなやつです。
require 'date' base_date = Date.new(2019, 5, 22) 0.upto(585) do |i| target_date = base_date + i start_commit = `git rev-list -1 --before="#{target_date} 00:00" current`.chomp end_commit = `git rev-list -1 --before="#{target_date} 23:59" current`.chomp # An expected output string format is like " 3 files changed, 8 insertions(+), 3 deletions(-)". stats = `git diff --shortstat #{start_commit} #{end_commit} ':(exclude)package-lock.json' ':(exclude)*.min.js' ':(exclude)*.css' ':(exclude)dist/*' ':(exclude)dev/vender' ':(exclude)src/lib/modules' ':(exclude)*.md'` parsed_stats = stats.split(",").map { _1.to_i } if parsed_stats.size == 3 puts [target_date].concat(parsed_stats).concat([start_commit, end_commit]).join(" ") else puts [target_date, 0, 0, 0].join(" ") end end
gitのcommitの日付を元にしています。 git rebaseなどで順序が入れ替えていると、本当にその日付にコミットしたとは限りません。 全体的な傾向がわかればいいので、ここでは無視します。
gunplotでグラフ化します。
gnuplot> set xdata time gnuplot> set timefmt "%Y-%m-%d" gnuplot> plot 'daily_git_stast.dat' using 1:3 with steps title 'insertions', '4col.csv' using 1:4 with steps title 'deletions'
4000行を超える変更をした日が二つあります。
次のRubyスクリプトを使って、あるリポジトリの2020年のGitコミット数を月単位で集計します。
require 'date' 1.upto(12) do |i| start_date = Date.new(2020, i, 1) end_date = (start_date >> 1) - 1 commits = `git log --oneline --since #{start_date} --until #{end_date} | wc -l` puts "#{i}, #{commits.chop.to_i}" end
結果は次のとおりです。
1, 60 2, 76 3, 32 4, 45 5, 19 6, 53 7, 47 8, 57 9, 138 10, 482 11, 432 12, 432
gnuplotを使ってグラフにします。
gnuplot> plot "./commits.dat" with line
10月からコミット数が通常の3倍に増えていました。