patorashのブログ

方向性はまだない

株式会社リゾームで昇進しました(2回目)

うちの会社は期首が9月で、今期で昇進した。世間では退職エントリーとか入社エントリーが多いから、自分は昇進エントリーを書く。(2回目)

なお、1回目はこちら。3年前ですね。

patorash.hatenablog.com

課長に昇進した

うちの会社ではキャリアとしてマネジメント職と専門職がある。前期までは専門職だったが、今期からはマネジメント職になる。マネジメント職になった経緯は、まぁ正直なところ横滑り的な感じではある。昨年、このポジションにいたカズさんこと id:tech-kazuhisa が退職したことで、傍から見ていた感じではあるが、チームが一気に硬直化してしまったように思えた。カズさんの存在の大きさがよくわかった1年だと感じた。

まぁそんなこんなで日々の雑談タイムや読書会などで彼らの話を聞いていると、色んな不安や不満の話が出てきたりするのだけれど、当時の自分は、違うチームのリーダーだったし、かといってこのままだとマズイなぁというのはヒシヒシと感じていた。 「こうなったら、私が課長やるかー」という気持ちになってきたので、打診したところ、じゃあ来期から課長で!ということになったのだった。

マネジメント職の職責

マネジメント職の職責としては、

  • 組織の上位方針に沿って部門目標を立てる
  • 部門目標を達成するための計画を立てる
  • 計画を遂行するための指揮を取る
  • 部門メンバーの様々なサポートを行う
  • 他部門との調整を行う
  • お金の関わる辺りのやりくり

あたりだろうか?当たり前だけれど、専門職とは求められるものは随分異なる。今までよりも、より成果を求められるので、チームをいかに強くしていくかにかかっていると思う。

課長になってやっていくこと

さきほど、チームをいかに強くしていくかにかかっていると書いたばかりだが、今期はそこはそこまで重要なポイントとは思っていない。 まずは差し当たっての不満・不安を解消していくことだと考えている。その要は部門目標である。

部門目標・個人目標を明確に

期が変わる1ヶ月前に、今期の体制が発表されるため、8月はまだ課長ではないものの、今期の課長として部門目標を立てたり、それを達成するための計画作成、そして部門メンバーの個々の目標作成などについてミーティングを重ねた。なにせ、雑談や読書会で一緒に話したりはしているものの、違うチームから来た人間が課長になるので、現場の感覚と一致していない目標を立てられたら白けるだろうと思ったので、自分が考えた叩き台の目標と計画を事前に説明し、現場の意見をもらいながら修正して現実的なところに落とし込んだ。しかしそれでもかなりアグレッシブな目標にしてある。

成果の定量

開発部門は目標をなかなか定量化しづらいのだが、達成基準として、

  • 期日を設定し、前倒しで開発できたら評価に加点
  • ○名が運用・保守に参加できる技能を習得する
  • 既存システムの改善ポイントを見つける毎に評価に加点

など、加点ポイントを多く盛り込んだ。 開発だと、大概のことが「業務なのだから、やって当たり前」とされてしまい、せっかく改善ポイントを見つけても、やるのは発見した本人になるし、やったところで当たり前だから評価のポイントにはならない。だったら見つけてもあまりやりたくない、という負のスパイラルを生みがち。 その人の職位にもよるけれど、そのあたりは評価されて然るべきと思ったので、まずはそのスパイラルを断ち切りたい。

定量化されていれば、それ以上の成果を出せた場合はアピールしやすいし、途中経過が芳しくなければ、ふりかえりで対策を練ることができる。加点を狙って動けるようにするのが目的。一見、評価気にしすぎじゃない?と思われるかもしれないけれど、評価の高くなる成果は組織にとって良いことなのである。しょうもないことを評価するようにしているわけではない。

コミュニケーション設計

コミュニケーションはコロナ禍になってから更に希薄になっていると思うのだけれど、もうちょい活発になってほしいなぁと考えている。かといって、なんでもいいから雑談しろってのは無理があるので、コミュニケーション設計していくからねという話はした。

まぁ簡単に書くと日報を書こうってことと、他のメンバーの日報に、できればなにかしらリアクションしましょうってあたり。 日報には他の目的もあって、1on1の際に何をやってきたかを蓄積できるので、より振り返りしやすくなる。

あまりコミュニケーションが薄いと、チームワークが発揮されにくい。逆にいえば、コミュニケーションを増やせば自ずとチームワークが発揮されるようになるはずなので、コミュニケーションを取らないと達成できない形の目標を作ったりもした(もちろん両者同意の上で)。

管理職としての決意

部門目標を達成することは大事だけれど、やはり現場で働くメンバーが楽しくやりがいを感じて仕事できるようにすること、そして、給与面での待遇改善が、管理職としての重要な役割だと思う。体制としては上司と部下であるけれど、本質は役割の違いでしかないと思う。だから、そこを気にして発言できないようなことにはしたくない。心理的安全性のある関係を作りつつ、チームワークを発揮して成果を出して、その成果はしっかりアピールして、メンバーが昇進・昇給できるようにやっていく所存です。

頑張るぞー!

ActiveRecordでテーブルコメントを参照する

小ネタです。migrationの際にテーブルコメントを追加する情報は検索すればいくらでもヒットするんですが、ActiveRecordでテーブルコメントを参照する方法はなかなかヒットしなかったので、メモしときます。

環境情報

方法

ヒットしたのはAPI Dockのこれ。 apidock.com

ConnectionAdaptersのメソッドです。引数にテーブル名を渡します。 モデルから直で取れるメソッドはなさそうでした。

connection = ApplicationRecord::connection
connection.table_comment(:users) # usersテーブルのテーブルコメント取得

User.table_nameみたいに、User.table_commentで取れてほしいなぁ〜。

Elasticsearchへの保存を非同期ではなく同期で行う方法

環境情報

DBに保存したらElasticsearchも更新されたい

Railsプロジェクトで、データベースへの保存を行ったら同時にElasticsearchへの反映も行いたい、というケースが出てきました。

Elasticsearchの利用には、elasticsearch-railsを使っているのですが、データの反映は簡単でした。

# Elasticsearchへの保存が失敗してもロールバックしたいので…
class Foo < ApplicationRecord
  def apply!
    ApplicationRecord.transaction do
      # なんやかんかして
      self.applied = true
      self.save!
      self.__elasticsearch__.index_document # => これでElasticsearchにも反映される
    end
  end
end

画面側で確認しても問題なし👍Elasticsearchを使う検索フォームで検索してもOK。

問題発生(テストが落ちる)

しかし、RSpecでテストを書いたら、落ちる…😥

require 'rails_helper'

RSpec.describe Foo, type: :model do
  describe '#apply!' do
    subject { Foo.create!(applied: false) }
    it '反映されること' do
      expect {
        subject.apply!
      }.to change { subject.applied? }.from(false).to(true)
      definition = Elasticsearch::DSL::Search.search
      definition.query do
        must do
          term applied: true
        end
      end
      result = Foo.__elasticsearch__.search(definition).records
      expect(result).to be_present # => ❌存在しないと言われる👻
    end
  end
end

調査

なんとなく、もしかして非同期更新か?と思い、雑に2秒待つようにしたら、テストが通るように!

require 'rails_helper'

RSpec.describe Foo, type: :model do
  describe '#apply!' do
    subject { Foo.create!(applied: false) }
    it '反映されること' do
      expect {
        subject.apply!
      }.to change { subject.applied? }.from(false).to(true)
      sleep(2) # => 2秒待つ…
      definition = Elasticsearch::DSL::Search.search
      definition.query do
        must do
          term applied: true
        end
      end
      result = Foo.__elasticsearch__.search(definition).records
      expect(result).to be_present # => 🟢通った👍
    end
  end
end

とりあえずテストは通るようになったけれど、テストにsleepとか入れたくありません。どうにか同期的にデータ更新できる方法はないものか…とコードを読んでいきます。

https://github.com/elastic/elasticsearch-rails/blob/main/elasticsearch-model/lib/elasticsearch/model/indexing.rb

Yardを読むと、@param options [Hash] Optional arguments for passing to the clientと書いてあるけれど、いやそのHashにどう設定すればいいかがわからん!😠

ググったら、zennの記事がヒットしました。

zenn.dev

コードの感じからすると、Kotlinかなんかだろうか…?とりあえずrefreshオプションがあることがわかりました。

www.elastic.co

解決方法

というわけで、該当箇所を修正。

# apply!メソッドの一部
self.__elasticsearch__.index_document(refresh: true) # => Elasticsearchに同期的に反映

これで、sleepなしでもRSpecが通るようになりました👍

テストの時だけ同期をとるようにしたい

しかし、なぜ非同期でElasticsearch側に反映されるかというと、パフォーマンス向上のためですね。つまり、毎回同期をとるように更新していたら、遅くなります。ループでFoo#apply!メソッドを呼ぶこともあるので、それは避けたい…。 そもそも、普段は即時同期が取れていなければならないほどシビアな条件でもありません。単に、テストの時だけ同期が取れていればいいわけです。

そこで、該当箇所を以下のように修正。

# apply!メソッドの一部
self.__elasticsearch__.index_document(refresh: Rails.env.test?) # => テストの時だけElasticsearchに同期的に反映

これで、テストの時だけElasticsearchへの反映が同期的になりました👏👏👏

「現場で役立つシステム設計の原則」読書会 vol.10

第10回の感想です。前回の感想はこちら。

patorash.hatenablog.com

会社のブログのレポートはこちら。

tech.rhizome-e.com

最後なのでみんな参加できるようにしようということで日程調整してから行ったので少しずれ込みました。というても5月前半だったので、私がブログ書くのが遅かっただけですね、はい。色々バタバタしてまして。もう次の読書会が始まってますし(汗)

10章 オブジェクト指向設計の学び方と教え方

  • オブジェクト指向の説明は意味不明というところ…。まぁそこまで意味不明とは思っていなかったんだけれど、ドメインオブジェクトを設計していくという考え方にはあまりなっていなかった…というか値オブジェクトを作っていくというところがあんまりなかった。受託開発の場合、リファクタリングをする機会などほぼ与えられないため、嬉しさはよくわかっていなかった。
  • オブジェクト思考の学び方、リファクタリグ。こっちのほうがいいと思う。
  • 極端なコーディング規則を作ると、1人ならいいのかもしれないけれど、チームでやるとイライラしそう。
  • 本の紹介があった。リファクタリング Rubyエディションは会社に置いてあるはず…。ただ、情報が古いので現在だと微妙かも。過去に有志で読書会やったけれど(当時の参加者は自分以外は転職してるが)、なんでやねんってよく言ってた。しかし今見返すと「なるほど」となるかもしれない…。
  • オブジェクト指向の考え方を理解する。のところ。この本に書かれていることで十分な気もするんだけれど。エヴァンスのドメイン駆動設計はよく紹介されている。Kindle積読してある…。書かれたのが2000年代初頭なので、それを念頭において読まなければならないと、texta.fmでt-wadaさんが言ってた。本に書いてある通りの実装方法が現代においては冗長だったりするらしい。難しい本なので、この本こそ、読書会で取り組んだほうがいいかもしれないと思った。

私はPodcastGoogle podcastで聴いてるので、リンク貼っておきます。

podcasts.google.com

雑感

エリック・エヴァンスのドメイン駆動設計は他の先輩社員にもオススメされていたという話が他チームの参加者からあった。まだちゃんと読んだことないので、いつか読んでみたいとは思っている。これも読書会とかでやらないと多分読み切れない気がする…。 どうやってオブジェクト指向を学ぶか?というテーマでは、「やっぱりリファクタリングしながらうまく構造化できたりして身についていくもんじゃないですかねー?」という感じだった。 とあるメンバーが、「実際に動いている業務コードがあるのだから、それを触れていくのが一番手っ取り早い。なので整地日(リファクタリングデー)を活用してやっていけたらいいと思うんですけど、整地日にみんなが取り組むのがドキュメントの整備とかが多くて、コードの整地がほとんど進んでない」と話してたと思う。まぁドキュメントの整備も重要なので、いいとは思うんだけれど、ずっとドキュメントの整備をしてたら進まないというか、ドキュメントの整備は整地日じゃなくて普通の業務の日に取り組むべきじゃないか?と思った。まぁそれはそう伝えたような…(ちょっと前なので記憶が曖昧)

この本を読んでる最中にリファクタリングしましたか?という質問には、あまりいい返事がなかったので、やや悲しい。私は得られたことがかなりあって、ガシガシとリファクタリングしていってたので、みんなも取り組んでるかなと思ってたので、返答が意外だった。しかし、後日、同じチームの後輩氏と一緒に取り組んだリファクタリングデーでは、後輩氏のコードはちゃんと本の内容を意識したコードになってるなと思った。あとやっぱりコードレビューのときの指摘の説明が簡単で済むのが助かる。開発もまた、ドメイン知識の共有なのだと思う。

読んですぐ手を動かす瞬発力を身につけさせるにはどうしたらいいのだろうか?

読書会全体を通して

今回から、事前に読んできてもらって、感想や疑問・他の人の感想に対する疑問・アドバイス、そこから派生して業務の雑談(自然とテーマは本の内容に近くなる)などを1時間~1時間半くらいでやってました。そして、テックブログも始まったので、せっかくだからこういうことやってるよとテックブログで発信していこうと話して、交代しながらアウトプットもしました。

なんとなく感じているモヤモヤを相談する場にもなっていたので、やった甲斐はあったかなと思います。解釈の違いに気づけたりもしました。それはやっぱり1人で読んでいるとわからない点ですね。

twitterでは、テックブログの記事について、者の @masuda220 さんにRTしていただいたりしたので、そのときはアクセスが増えてました👍ありがとうございます。

オブジェクト指向言語でどう開発していけばいいかを網羅した素晴らしい書籍だと思います。新人教育など、ある程度経ってから、また読書会を開いていけたらいいなと思いました。

Herokuへのデプロイが成功しているのにgitのエラーが発生する件に対応した

git pushでHerokuにデプロイしたら、デプロイは成功しているのに、最後にエラーメッセージが出てしまう現象に遭遇しました。

$ git push heroku release-yyyymmdd:master
# なんやかんやあって

remote: Waiting for release.... done.
fatal: protocol error: bad line length character: fata
error: error in sideband demultiplexer
error: failed to push some refs to 'https://git.heroku.com/xxxxxxxxxx.git'

いや、release..... done.ってなってるやないか。なんで最後エラーに…。このせいでデプロイの自動処理がここで止まってしまう…。

fatal: protocol error: bad line length characterググると、.bashrcでechoしてないか?とか、色々出てくるんですが、確認したところ、そういうこともしていませんでした。

エラーメッセージと、herokuで検索したら、それっぽいのをstackoverflowで発見!

stackoverflow.com

これのコメントにある通りに対応したら、直りました👍

$ heroku plugins:install heroku-repo
$ heroku repo:reset -a xxxxxxxxxx
$ git commit --allow-empty -m "Reset repo"
$ git push heroku release-yyyymmdd:master

Herokuのリポジトリをリセットしたら直ったってことは、なんか壊れていたってことなんかなぁ〜🤔と思いつつ、これで終わりとしたいと思います。

「現場で役立つシステム設計の原則」読書会 vol.9

読書会自体はGW前にやっていたのですが、諸々あって書くのが遅くなってしまった…。

第9回の感想です。前回の感想はこちら。

patorash.hatenablog.com

会社のブログのレポートはこちら。

tech.rhizome-e.com

9章 オブジェクト指向開発のプロセス

  • 開発の基本はV字モデルではあるが、そのサイクルが異なる。今はアジャイルで開発することが主流なので、同じチームで全部を担当することが普通になっている。大体うちでもそういうプロセスでやってる。
  • しかし、ここに書いてある通り、分析・設計にほとんど時間をかけずにとにかくプログラミングするという流れはある。特に、Railsでは顕著ではないかと思う。ちょっと規模が大きくなるとコードの見通しが急速に悪化するというのは、見に覚えがある。
  • ドメインモデルを中心にしたソフトウェアの考え方。従来の開発の仕方は、今いるメンバーは殆ど経験したことがないんじゃなかろうか?オブジェクト指向らしい開発の進め方について。設計した人が開発もするのでドキュメントの確認も減るし大変効率が良い。
  • 口頭でのやりとりをラフスケッチとしてホワイトボードに起こしていくというのは、写真を撮るだけでいいしよくやっていた。うちの会社だとスマホで写真を撮って共有、がやりにくいが…。リモートワークが主流になった現在だと、そういうツールを使いこなせばいいのかもしれないが、あんまりやってない。Miroとか?一応MS Whiteboardはある。ペン付きのPCならいいんだけれど、Macだとそのあたり厳しい。
  • ドキュメントに関して。データベースのテーブル名/カラム名とコメントは書くようにしている。I18nのデータを元にコメントにしていく仕組みを準備している。gemにしてもいいかもしれない。
  • 全体を俯瞰するドキュメント。これはインセプションデッキと役割が似ている。時々見返したり、見直したりする必要がある。
  • 技術方式のドキュメントもソースコードで表現できるというところ。Infrastructure as Codeの話。現代においては、この辺りはもう全てコード化しておくべきもの。学ばなければならないものが多い反面、自動化できることの恩恵は大きい。
  • 分析と設計が一体になった開発のやり方をマネジメントする
  • 受託開発の行うときに発注する側、受注する側で気を付けるべき点等が書かれているのはよい。準委任契約のほうがいいという点は、そうだろうなぁと思う。
  • 進捗の判断…チケット単位でドメインオブジェクトを作るように組んでいけば、この通りに進捗管理できるんだろうか?ドメインオブジェクトの組み合わせて機能を作っていけるので、例え機能が未実装でも、ドメインオブジェクト作成の進捗があれば、進んでいるとみなせるということか。その発想はなかった。
  • 品質保証について。ドメインオブジェクト単位で独立しているから、テストしやすく、ミスがあったとしても簡単に修正できる。
  • 他の章にも書いてあったが、プログラミングスキルとドメイン知識の両方を備えていかないと、優秀なエンジニアとは言えないと思う。業務知識の勉強会を行うことも大事と思われるので、そういうことも計画していったほうがよいかもしれない。

雑感

読書会のメンバーは自社製品の開発をしているので、受託開発する際の契約に関するところは、「うーん?」という感じであった。設計と、設計レビューにもっと時間を割いて、熟練者の知見を吸収していったほうがいいんでないか?ということを話したように思う。大して設計せずにすぐ作り込んでから、実装に対するレビューをしてしまうとレビューの視点もブレるし、実装者もあんまり変更したくないもんだから設計がおかしいまま実現しようとしてしまいがちだと思う。わかったふりをせずに、貪欲に質問したほうがいい。みんな答えてくれるから。

「どうやって業務知識をつけていったんですか?」という質問があった。「扱っている製品が違うので、一概には言えないんだけれど、定例会のタイミングとかで疑問をぶつけたり、お客さんからの反応を聞き出したり、おすすめされた記事を読んだり等はしていたよ」という話をしたり等。先輩社員にドメイン知識をどうやってつけたのか?を聞いてみたら、ヒントがあるのではないか?と話したら「そういえばあんまり聞いたことがなかった」ということだったので、聞いてみよう!ということに。

RSpecでブロック引数をmockで置き換える

RSpecで、ブロック引数に渡されたオブジェクトが、とあるメソッドを実装しているかどうかによって処理を変える件のテストをしたかったのだけれど、どうやったらいいかわからなかったので調べました。

テストしたい処理

class Hoge < ApplicationRecord
  def foo
    # 略
  end
end

class Fuga

  # @param [Class] model_class ApplicationRecordを継承したクラス
  def initialize(model_class)
    @model_class = model_class
  end

  def execute!
    @model_class.find_each do |record|
      # fooメソッドが実装されていたらfooを実行したい
      value = if record.respond_to?(:foo)
                record.foo
              else
                record.bar
              end
      # 略
    end
  end
end

これで、model_class#fooがあれば、それが実行されていることを確認したかった。

やったこと

【NG】stubでnewの戻り値をmockに置き換える

当初はHoge#newメソッドを置き換えておいたらええんちゃうか?と思ったけれど、ダメでした。binding.pryfind_each内のループに仕込んでも到達しなかったので、やめました。

RSpec.describe Fuga do
  context 'fooメソッドがある場合' do
    let(:record) { instance_double(Hoge) }

    before do
      allow(Hoge).to receive(:new).and_return(record) # NG
      # record.respond_to?(:foo) の戻り値をtrueにstubしておく
      allow(record).to receive(:respond_to).with(:foo).and_return(true)
    end

    it 'Hoge#fooが呼ばれること' do
      expect(record).to receive(:foo)
      described_class.new(Hoge).execute!
    end
  end
end

【OK】stubでfind_eachのブロック引数をmockに置き換える

and_yieldを使えば、ブロック引数を置き換えることができることを知りました。

RSpec.describe Fuga do
  context 'ApplicationRecord#fooメソッドがある場合' do
    let(:record) { instance_double(Hoge) }

    before do
      allow(Hoge).to receive(:find_each).and_yield(record) # OK
      # record.respond_to?(:foo) の戻り値をtrueにstubしておく
      allow(record).to receive(:respond_to).with(:foo).and_return(true)
    end

    it 'fooメソッドが呼ばれること' do
      expect(record).to receive(:foo)
      described_class.new(Hoge).execute!
    end
  end
end

これで無事にHoge#fooが呼ばれることを確認することに成功!✌️

参考にしたページ