何でも屋エンジニアのブログ

ソフトウェア関連技術、コミュニティ、日々の雑貨

CodeKeeper v0.5.1をリリースした

v0.5.0をリリースした翌朝、ふとClass Lengthのメトリクス結果にNamespaceを付与してなかったことに気づいた。これではUserクラスもAdmin::Userクラスも同じUserになってしまうのでマズい。ということで以下のPRで直した。もし困っていた方いたらアプデしてもらえるとありがたいです。

github.com

いざとりかかってみると、ネームスペースを取得していないだけでなく、

class Root
  class A
    # このコメントのカウントが間違っていた
  end
end

のようなinner classがありそこにコメントがある場合、Rootクラスのコメントとして計上してしまうバグや、以下のように3つ以上ネストした場合

class Root
  class B
    class C
    end
  end
end

Rootクラスの行数を誤ってしまうバグなどを発見したので併せて修正した。

大変だったのは、コメントや空行の数を数える際、Inner nodeと親nodeで二重計上しないようにすることだった。例えばコメントの場合、Rubocop::AST::ProcessedSource#commentsでソース内のコメントの行数を把握し、node.first_line..node.last_lineのRangeに入るものであればあるnodeのコメントであると判断しても良いように思えていたが、そのクラス内にネストされたクラスを持ちコメントがある場合、当然二重計上となってしまうのであった。

この辺の煩わしさを避けるため、空行とコメントを数える際はInner nodeの行数を配列に持ち、あるnodeの本体が何行目にあるのかを正確に取得するようにした(余談だがRangeとArrayの変換や演算がとても便利だった)。

Namespaceに関して、上述したclass A;endのような記述には対応したが、Class#newやStructを利用した記述については、別実装となるため対応しなかった。なので

Class Root
  SubClass = Class.new
end

SubClass = Class.new

も両方SubClassと表示される。とはいえ、メトリクスを取りたいクラスでこのような記述、特にStructを使った記述はしないだろうと予想しているので、大きく困ることはないのではないかと思う。

コードの品質に関するメトリクスを取得するGemをリリースした

CodeKeeperというGemをリリースした。循環的複雑度、ABCソフトウェアメトリクス、クラスの行数という品質面にまつわるメトリクスを取得するGemで、Rubyファイルを対象にしている。

github.com

動機

主に以下の3つである。

  • Four keysのような生産性を測る指標とは別に、内部的な品質に関する指標を取りたかった
    • 継続的な改善を続けた結果としての変化を見たかった
  • コードを解析するコードを書いてみたかった
    • Saasなどもあるが自分で書いてみたかった
  • Gemを1から書いて公開したことがなかったのでやりたかった

使い方

メトリクス・出力形式

対応しているメトリクスは

  • 循環的複雑度(ファイル)
  • ABCソフトウェアメトリクス(ファイル)
  • クラスの行数

である。

前者2つは、実装の簡便さを鑑みファイル単位とした。また、出力について、取得したメトリクスをBigQueryなどに取り込めるように、csvもしくはjsonにて標準出力に吐き出す形とした。

設定

以下のような設定で、

  • 取得するメトリクス
  • 実行スレッド数
  • 出力フォーマット

を選択できる。

CodeKeeper.configure do |config|
  # If you choose metrics, specify as follows:
  config.metrics = %i(cyclomatic_complexity abc_metric class_length)
  # The number of threads. The default is 2. Executed sequentially if you set 1.
  config.number_of_threads = 4
  # The default is json
  config.format = :csv
end

以下は例としてGitlabの適当なファイルに実行した結果である。

$ bundle exec code_keeper app/models/user.rb app/models/ability.rb > metrics.csv 
$ cat metrics.csv                
metric,file,score
cyclomatic_complexity,app/models/ability.rb,9
cyclomatic_complexity,app/models/user.rb,24
class_length,Ability,86
class_length,User,1494
abc_metric,app/models/ability.rb,76.909
abc_metric,app/models/user.rb,1549.264
$

調べたこと

CodeKeeperを作るにあたり、以下をキャッチアップした。 コード解析やASTについてはRubocopのソースやRubocopAstのドキュメントを読み、実際に手を動かし挙動を確認した。その際pockeさんの記事にはとても助けられました。ありがとうございます。 qiita.com

pocke.hatenablog.com

Class length metricを作るときに参考にした rubocop/class_length.rb at master · rubocop/rubocop · GitHub 周りのコードが特に勉強になった。一行一行動かしながら意味を理解するのも楽しかった。return unless block_node.respond_to?(:class_definition?) && block_node.class_definition? の意味がわからなかったが以下のissueを見てなるほどーとなった。 github.com

実装の多くはRubocopを参考にしており、本当に感謝しかない。

メトリクスについて、株式会社Sider様のブログがとても参考になった(ありがとうございます)。基本的にはその記事のリンクを辿ったり、GMetricsの対応しているメトリックを調べたりして勉強した。

siderlabs.com siderlabs.com

dx42.github.io

作ってみて

Rubocopに依存しすぎている(private apiも使っている)のではがして実装しようと思っていたが、Rubocopがとてもよくできているし、中身読んで理解するだけで勉強になるしこれで良いかなという気持ちや、細かいところしっかり考えるのかなり大変そうという理由もあり、現在はRubocopに依存しているMetricsが多い。とはいえ、Rubocopでは内部的にMetricを計算した後違反の有無に応じて結果を保存しているため、結果(スコア)を取得することができずカスタマイズする必要はあったが。

また、アプデに追従するの大変かもしれないとも考えたが、依存先がそこまで多くないし問題にはならないだろう。そして作って感じたことだが、Rubyの表現力が高くバグにつながるパターンは(自分のRuby力では)ある程度ユーザーに見つけてもらう必要があり、そう考えると既に利用者が多くFBに対しきちんと対応しているRubocopに依存するのも悪くない選択肢だろうと考えた。

その他

Rubyでどう書くか、そしてそれがどう構文解析されるかに関する理解が深まるので、静的解析に関して遊ぶのは結構有益だなと感じた。

余談だが、README.mdにも書いた通りメトリクスを取得することはコードをきれいにすることに繋がるのでCodeKeeperという名前をつけたが、キレイにするのは人なので単純にRMetricsとかにすれば良かったかなと思っている。

育児休業を取得している

8月から半年予定で育児休業を取得しているので、現時点の雑感を残しておこうと思う。

育児休業かなり多くのメリットを感じているし、子供と過ごす時間がとても貴重なので本当に取得して良かったと感じている。

生後一ヶ月頃から私は育休を開始した。育児休業に入る前から積極的に育児に参加していたため、家の中で行う分には特に困ることはなかったものの、何事も二人でやったほうが楽なので、担当が二人いるというのはかなりメリットだった。「家の中で行う分には」と書いたことからも分かるように、慣れない場所でのお世話の難易度は家で行うときよりもはるかに高かった。育児休業開始時点で子の外出許可が出たので、育児休業開始と外出し始めが同時期だったが、育休のタイミングでたくさん外出し経験値を上げられたのはとても良かった。持ち物や必ず近隣の授乳室を探しておくなどの知見もたまり、今ではかなりスムーズに外出を楽しめるようになった(持ち物に関しては妻が対応してくれているのでとても助かる)。

また、娘は比較的敏感な子なので子育てに関する負荷は高めで、特に夜はまぁまぁキツイ。今は交代で見ているが、どちらかが働いていて一人で全部見る状態になると考えると、かなり厳しかっただろうなと思う。

実務的なメリットを上げてきたが、感じるメリットは他にもある。

一般的かどうかは分からないが、赤ちゃんは非線形な成長をするように見える。例えば、ある日を境に

  • 表情が豊かになる
  • 声を出すようになる
  • なにかに興味を持つようになる

など、突然急に何かが変わるのである。

継続して寝ることをあまりしない娘がミルク飲む以外ほぼ一日寝てる日があったのだが、その日を境に色々できるようになることが増え、顔つきも変わったということがあった。

非線形な成長と言ったもののそれらは本当に些細な変化であり、毎日一日中面倒を見ているからこそこの変化に気付けるのだろう。このような日々の変化を妻から聞くのではなく、自分で気づきリアルタイムで感動できるのは間違いなく育児休業のおかげである。このような体験が子育てで最も嬉しい瞬間の一つであり、まさに貴重な体験だと思う。

この記事、「さすがに育児休業取って3ヶ月くらい経つしさすがに書かないと...」と思って書いてるあたり、どれだけ密度の濃い時間を過ごせているかが分かる(まだ一ヶ月しか経過してなかった…)。

ところで、デメリットについて、心配なのは金銭面と仕事復帰への不安だが、まだ一ヶ月なので大きな問題だとは感じていない。また何かあれば書こうと思う。

RuboCop::AST::ProcessedSourceのAPIを触るときのメモ

コードのメトリクス自分で取りたいなと思いRubocopのソースコードを読んでいる。

Copが呼ばれるまでにソースコードがどう処理されて渡されるのか、Cop内でどう解析するのか気になって手元でいじっていた。

手元でRubocopを実行しブレークポイントを貼り見るのも良いけど、processed_source周りのAPIを自分でたたいて遊びたかったので以下のようなスクリプトを書いた。呼び出すcopの設定をしたりキャッシュをoffにしたりとちょっと面倒に感じたのもあるが、pockeさんの記事を読めばクリアできるのであくまでも副次的な理由である。

qiita.com

# 標準入力からソースコードを渡している
require 'pry'
require 'rubocop'
require 'rubocop-ast'

arg = ARGV.first
source = +arg

ps = RuboCop::AST::ProcessedSource.new(source, 3.0)

def visit_depth_last(node, &block)
  node.each_child_node { |child| visit_depth_last(child, &block) }
  yield node
end

visit_depth_last(ps.ast) do |child|
  binding.pry
end

Rubocopの各所で引数として渡されているnodeはprocessed_source.astなので、visit_depth_lastに渡してあげてゴニョニョするとRuboCop::AST::*NodeAPIを触れるようになる。これで自分の参考にしたいCopの実装を見つつ手元でデバッグするのが分かりやすかった。

React.js docs読んでいるのでメモ

今改めてReact.jsを学びなおしている。

以前チュートリアルをやって分かったような分からないような気持ちだったので、ドキュメントを読んでいるが詳しく解説があって読みやすい。

以下のページから1ページずつ読んでいるのでそのメモ。h3は雑にページのタイトルだったり項のタイトルだったりする。

ja.reactjs.org


要素

Ractアプリケーションの最小単位の構成ブロック

React要素はプレーンオブジェクト(notブラウザのDOM要素)

要素をDOMとして描画するには

ReactDOM.render() にelement(React要素)とHTML内の要素を渡す(document.getElementById('root')

DOMやHTMLとの関係がよく分からなくなったときは:

Document Object Model (DOM) は HTML や XML 文書のためのプログラミングインターフェイスです。ページを表現するため、プログラムが文書構造、スタイル、内容を変更することができます。 DOM は文書をノードとオブジェクトで表現します。そうやって、プログラミング言語をページに接続することができます

https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Introduction

レンダーされた要素の更新

React DOM は要素とその子要素を以前のものと比較し、DOM を望ましい状態へと変えるのに必要なだけの DOM の更新を行う

コンポーネントとprops

コンポーネント: JavaScriptの関数のようなもの。任意の入力を受け取りReact要素を返す。任意の入力はpropsと呼ばれる。

関数コンポーネントとクラスコンポーネント

クラスコンポーネント: ES6クラスを利用したもので、React.Componentをextendしrenderをオーバーライドする

コンポーネントのレンダー

ユーザー定義のコンポーネントをレンダーすることもできる。具体例は: https://ja.reactjs.org/docs/components-and-props.html

コンポーネントを組み合わせる

function Welcome(props) {
  return <h1> Hello, {props.name}</h1>
}

function App() {
  return (
    <div>
      <Welcome name='Sarah' />
      <Welcome name='Taro' />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Propsは読み取り専用

すべてのReactコンポーネントは自己のpropsに対し純関数のように振る舞わなければならない。

state とライフサイクル

「何が起こったのかをメソッドが呼び出される順序にそって簡単に振り返ってみましょう」の箇所は繰り返し読むこと。

データは下方向に伝わる

単一方向の(トップダウンの)データフロー

flex-git-configにGithub ActionsでCI/CD環境を整備した

flex-git-configを作ったが、CI/CD環境もなくバイナリも手動でリポジトリにcommitしていた。

同僚から便利じゃんって言ってもらえたのと、Github Actions使いたいと思いつつ機会がなかったので利用し導入してみた。 併せて、雑だった各種メッセージや終了時の挙動を改善した。

ちなみにflex-git-configを作成した動機は以下に書いている。 blog.ebihara99999.com

作成にあたり以下を参考にさせてもらいとても助かった。 qiita.com

Golang、あまり業務で使うことが少ないんだけどflex-git-configを盆栽にして細々とメンテしていこうと思う。

開発環境でActionController::InvalidAuthenticityTokenが出るようになった

結論

SameSite=None; Secureにしているサービスは開発環境もHTTPS化しておきましょうというお話です。Chromeは84よりデフォルトの挙動が変わり、FirefoxFirefox 69からSameSite属性が利用できるようになっています(デフォルトの挙動はまだのよう)。

自分の担当しているサービスでもメンバーが既にHTTPS化してくれていましたが、HTTPとHTTPS両方使える状態だったので今回の事象に遭遇してしまいました。

経緯

初回リクエスト時のレスポンスを返す際に生成したCSRFトークンがsession[:_csrf_token]に格納され、次のリクエスト時には当該データとリクエスト時に受け取ったCSRFトークンを突き合わせリクエストの整合性を担保している。

具体的にはこのあたり。

def real_csrf_token(session) # :doc:
  session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
  Base64.strict_decode64(session[:_csrf_token])
end

出典: actionpack/lib/action_controller/metal/request_forgery_protection.rb

しかし2回目のリクエスト時にはその前に生成されているはずのCSRFトークンがsession[:_csrf_token]に格納されておらず、不正なリクエストと判定されているようだった。Safariで確認したところ想定通りのタイミングでsession[:_csrf_token]に値が格納されていた。

自分で挙動を調査しチームメンバーにヒアリングしたところ

  • HTTPSでログイン可能(上記不具合はHTTPによるアクセス)
  • Chromeではログインできる人とできない人がいる(HTTP)
  • Chrome以外のブラウザではログイン可能(HTTP)

という状態だった。

HTTPSでログイン可能という挙動とデバッグの過程から、SameSite Cookiesの機能ではないかと疑った。 Chromeでログインできるメンバーに確認したところその人だけバージョン63のChromeを使っており、ログインできない組は64を使用していた。確認したところ、新型コロナウィルスの影響で延期されていたSameSite cookieの対応が7/14 バージョン84から再開されており、その影響であることが判明した。

参考

qiita.com

www.chromium.org

web.dev