やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

「なのでは」「ではないかと」って言い方やめてくれ

単純に不快

なんでわざわざ「なのでは」とか「ではないかと」っていう文章を送ってくるのか理解出来ません。
これが攻撃的な言い方に感じるのは自分だけでしょうか。日々、マウントをとってくるような人が使っているイメージがあります。

  • 「なのではないでしょうか」
  • 「ではないかと思います」

と表記すればいいものを...。面倒なのでしょうか。 Slackで「なのでは」「ではないかと」という返信がスレッドにされていると不快に感じてしまいます。。。

イライラするのは自分だけ?

と思って、Google先生なのでは 嫌い, なのでは うざい と調べてみたら結構ヒットしました。

www.google.com

www.google.com

みんな同じような理由で怒っていますね。これって修辞疑問って言うんですね。

~なのでは(ないでしょうか)?、という言い方を修辞疑問というそうです。
余韻を残す効果もあるのですが、おっしゃるとおり断定を巧妙に避けているともとれるため、論文などには不向きな語法とされています。

detail.chiebukuro.yahoo.co.jp

修辞疑問

修辞疑問とは、疑問文の形をしているにもかかわらず、相手に何かを訪ねているのではなく、
自分が言いたいことを相手に納得させるために反語的に疑問形で言い方です。 
肯定形の修辞疑問では否定の意味に、否定形の修辞疑問では肯定の意味になります。

なるほど。イライラしてしまうのは正常な反応な気がしてきました。

相手に何かを訪ねているのではなく、自分が言いたいことを相手に納得させるために反語的に疑問形で言い方です。

単純に使うのやめてほしい

お互いに思いやりを持って仕事をしたいですね。

Rubyのコードレビューでよく指摘すること

コードレビューはじめました

昨年の10月から勤務先企業で開発チームのリーダーになりました。元々はコードをガリガリ書くような業務をしていたのですが、ポジションが変わったため、業務の内容も変化しました。大きく変化した点としてコードをレビューしてもらう側から、コードをレビューする側になったというのがあります。実際にレビューをする側になって、気づいたことはたくさんあります。

そんな中で半年もレビューをしていると、ある程度、指摘するパターンが見えてきましたので記事にまとめておこうと思います。これが必ずしも正解というわけではなく、こういう見方もある、過去にバグが続出した際に、以下で紹介する方針をとって少しずつ改善してきたという実績を元に観点を選別しています。
自分で言うのもなんですが、レビューを行なっているコードは稼働2年以上でお客様がかなりいらっしゃる現役で稼働しているサービスのものです。

よく指摘すること

随時、更新中です。

ifがネストされすぎている(2階層以上)

プロジェクトが肥大化したり、年数が経ってくると仕様が複雑になることが多いです。結果的に設定やオプション項目が複雑化して時には複雑な条件式を作成する必要があるかもしれません。 そんな時に、条件をネストしなければいけないケースが考えられます。サンプルでコードを用意しました。分かり易さのため、煩雑さを目立つようにしました。

def set_setting_option(setting)
    mode = setting.mode
    if mode == "normal"
        if setting.options.nil?
            if setting.plan == "free"
                setting.options = { :allow: false, :details: false }
            else
                setting.options = { :allow: true, :details: false }
            end
        end
    elsif mode == "pro"
        if setting.options.nil?
            setting.options = { :allow: true, :details: true }
        end
    end
end

幾重にも条件が分岐かつネストしているため、見ただけで「うっ...」となります。また、新しくmodeが追加された時、optionsの内容が変化した際の拡張性も悪く、保守性も微妙ですね。このようなコードはレビューの対象となることが多いです。

対処としてはifを用いて処理を分岐させるだけではなく、関数を細かく作成して、やることを分散させてあげることで、それぞれのmodeの処理の追いやすさと拡張性を確保しています。また、早期returnは条件式の複雑さ、ブロックのネストを避けられるケースが多いので、使用を推奨しています。リーダブルコードでも同じような指摘がありました。

改善案(一例)

def set_setting_options(setting)
    mode = setting.mode
    case mode
    when "normal"
        set_normal_seting_options(setting)
    when "pro"
        set_pro_seting_options(setting)
    else
        raise "存在しないプランが設定されています👀"
        
    setting
end

def set_normal_seting_options(setting)
    return setting if !setting.options.nil?
    if setting.plan == "free"
        setting.options = init_options(false, false)
    else
        setting.options = init_options(true, false)
    end

    setting
end

def set_pro_seting_options(setting)
    return setting if !settings.option.nil?
    setting.options = init_options(true, true)
    
    setting
end

def init_options(allow, details)
  { :allow: allow, :details: details }
end

init_optionsに関してはオプションの項目が増えた際に、引数が増えてしんどいことになるので、ハッシュを渡すようにするなど改善の余地はまだまだありそうです。

eachが多用される

配列に対する処理として何故かeachの関数が好んで使用される傾向があることに気づきました。確かに、eachだと色んな処理が表現出来るというのは分かりますが、それぞれの関数は役割を想定して作成されているので、適材適所で最もシンプルに記述できる関数を選択してあげるのが良いです。

def filter_pro_mode(settings)
    new_settings = []
    settings.each do |setting|
        if setting.mode == "pro"
            new_settings << setting
        end
    end
    
    new_settings
end

こういったコードを本当によく見かけます。この場合は元の配列からmodeprosettingを取得したいだけなので、filterを使ってあげるのが良い選択です。

def filter_pro_mode(settings)
    settings.filter { |setting| setting.mode == "pro" }
end

非常にスッキリしました。また読み手にはfilterだから要素を絞っているというのが、関数を通して伝えることが出来ます。eachの場合には処理の中身を詳細に追う必要がありました。

関数名と実行内容が一致していない

いまだに自分もこれをやってしまう時があります。関数には何をしているかが名前から分かるように命名をしてあげるのがベストです。ただ、処理が複雑なものや、言葉で表現が難しいものがあるのも事実です。ただ、以下のような場合ではレビューの対象としています。

def check_set_mode(setting)
    mode = setting.mode
    if setting.mode != "pro" && setting.mode != "normal"
        setting.mode = "normal"
    end
    
    setting
end

この関数はcheckといいつつ、setting.modeの値を上書きしています。関数名と行なっている処理が一致していないことが分かります。この場合にはoverride_set_modeという名前が良いのではないでしょうか。

インスタンス変数が多用される

インスタンスの作成時に引数を渡して、必要な情報をインスタンス変数としてclass内に保持しておく実装は頻出で便利な書き方です。しかし、便利なインスタンス変数をよしなに使ってしまうケースがあります。インスタンス変数はインスタンス内では広いスコープを持っているため扱いには注意が必要です。

僕のおすすめとしては、インスタンス作成時に渡した値は読み取り専用で、以降の処理でインスタンス変数の値を極力、更新しないようにすることです。インスタンス変数の更新は思わぬバグを生み出します。関数の実行順序が保証されない場合は特に注意が必要です。以下のサンプルではvalidate_and_set_settingの後に別の関数が呼び出されてしまうと、@useradmin_userだった場合に挙動が変わる可能性があるので注意が必要です。

どうしてもインスタンス変数を更新したい時は専用の関数を用意する(setter)という形でレビューを通すようにしています。

class SettingAdjusetment
    def initialize(setting, request_user)
        @setting = setting
        @user = request_user
        @mode= setting.mode
        @plan = setting.plan
        @options = setting.options
    end
    
    
    def validate_and_set_setting
        return false if @plan != "free"
        return false if @options != { :allow: false, :details: false }
        
        # インスタンス変数への更新処理がある...
        if @user.admin_user?
            @mode = "pro"
            @plan = "paid"
            @options = { :allow: true, :details: true }
        end
        
    end
end

(どう書いてもインスタンス変数を避ける姿勢が身についていつので、良いサンプルを用意するのが難しいです...😅)

無理に共通化しない

複雑な条件を書いて、処理を分岐させるぐらいなら関数を分割してしまった方が早いです。過去の事例でいうと、引数で渡された値のクラスによって処理を分岐させるというコードがありました。

def update_content(content)
    if content.instance_of(String)
        content = "string :: #{content}"
    elsif content.instance_of(Number)
        content = "number :: #{content}"
    else
        content
    end

    content
end

まずinstance_ofという関数が認知の低いもので、見ただけでは何をする関数なのか分からない可能性があります。合わせて、それぞれのクラスによってどのような処理がされるのかもコードを追わないと分かりません。今回はたまたま、同じような処理がされていましたが、今後、ArrayHashの場合には、異なる処理が適応される可能性が考えられます。

このように無理に共通化をすると混乱を生み出す可能性がありますので、レビューの対象としています。改善案としては細かく関数に分割することです。

def update_string_content(content)
    "string :: #{content}"
end

def update_number_content(content)
    "number :: #{content}"
end

複雑だった判定は呼び出し側で、任意に使用する関数を選択させるようにします。そのためにも分かりやすさのために命名が重要です。

unlessを可能な限り使わない

RubyElixirではunlessを使用することが出来ますが、言語によってはサポートされていないことがあります。また、unlessで表現出来る条件式は大体がifで表現することが出来ます。ifだと条件式が長くなってしまう場合にはunlessを許可することもありますが、unlessは普段ifで慣れている分、頭が混乱するので、レビューの対象としています。

温度感としては「使うな」というわけではなく、「最終手段として使ってね」という方針にしています。

総括

ベースの考えになっているのは、「明日記憶喪失になっても読めるコード」です。プロジェクトも肥大化しており、仕様もどんどん複雑になっていきます。そんな中に今回紹介したようなコードが紛れていると、複雑、拡張性がない、把握に時間かかる、潜在的なバグがある...など様々な問題を引き起こす可能性があります。

自分の仕事は様々な問題を事前に検知して、可能な限り取り除くことです。経験則に頼る部分は多いのですが、基本的なことは書籍でも広く紹介されています。自分はリーダブルコードや関数型のパラダイムが非常に役立っているなぁと思っていますので興味があれば、手にとってみて下さい。

参考文献

TODOコメントってあんまり意味がないんじゃないか説

とりあえず書き込まれるTODO

ある日のことです。新機能開発のためにコードを眺めていると該当箇所に対応するテストコードが全くないことに気づきました。 「TDDでやってるのになぁ」と思っていると、テストコードのファイルだけが作られていて、中にTODOコメントで想定されうる全てのパターンが記載されていました。

その数も中々のもので、ファイル内でTODOを検索してみると40件近く、TODOが書き込まれていることに気づきました。「〜したい」「〜の場合どうするか」という誰かが残したであろうTODOコメントが各所に溢れていました。

TODOあるなぁ。で、何をすればいいのか

TODOコメントがあって、後に何かをやる必要がある、やってほしいというのは分かるのですが、それがいつ発生した問題で、どのように解決するのか、他に関連する箇所はないのか、今は問題となり得るのか...と考えることがたくさんあります。

しかしながら、ただTODOと記述がされていても、上記の情報を得ることは難しいです。コメントを記載した人が詳細を覚えていればラッキーですが、そうでなければ何をしていいのか分からないコードに残された永遠のTODOとなってしまいます。

TODOという名前のくせに永遠にDOされることはないのです。

_人人人人人人人_
> 永遠のTODO <
 ̄Y^Y^Y^Y^Y^Y^Y^ ̄

generated: > 突然の死 < ジェネレーター

TODOは使わない方がいいのか

使うのは問題ないと思いますし、動作に影響を与えるものでもありません。また実装時間というのもプロジェクトによっては大きく制限もあるかなと思います。 しかし、ただTODOを書くだけでは、何が問題で何が重要なのか、いつまでにやる必要があるのか...など伝えられる情報が限られてしまいます。

なのでTODOコメントを使う場合はチームでフォーマットを定義しておくのが良いのではないでしょうか。自分はこんな感じでTODOコメントを書くようにしています。

# TODO: 2021/05/31 -> 来月まで
# validationが弱い。空白文字が入っているとそのまま登録されてしまうのでtrimする。

どうでしょうか。記載日と記載時に想定した締め日を1行目に書いてあげて、2行目に何が問題で何をすればいいのか書いておきます。
改めて記事にしてみて思ったのですが、TODOに優先順位が付けられるように情報が添付されているという状態が望ましいかなと感じました。

参考になればと思います。

参考文献

プログラマが知るべき97のこと

またブログを頑張るための振り返りと反省

サボりがちになったブログ

思えば、昨年には一週間に最低でも1記事。一昨年には何と一週間に2、3個の記事を書いていました。
時期としては大学の学部を卒業して新社会人としては働き始めた頃で決して、時間的に余裕があったわけではないはずです。さらに今と異なり、当時はリモートワークではなく毎日、片道1時間半かけてオフィスに通っていました。そんな状況で、記事を書いていたのに今はどうでしょうか。

リモートワークになり、参加しているプロジェクトも当時と異なり、かなり落ち着いた状況で進行しています。
なのにブログを書くことにたいして消極的になってしまいました。今回は言い訳と振り返りをしつつ、またブログを頑張っていくための糧にしようかなと思います。

なぜブログを書かなくなったのか

振り返ってみたところ、2つほど該当する理由を思いつきました。どれも言い訳にしかなりませんが、記しておこうと思います。

求められるスキルが変わった

昨年の10月からだったでしょうか。
今まで開発者としてガンガンコードを書いていたのですが、なんだかんだで業界4年目、勤務先企業でそこそこ籍を長く置いているという理由から、開発チームのリーダー(実態はメンバーのマネジメントとプロジェクト管理)になりました。メンバーは10人以下の小規模なチームです。
スクラムという開発方式を採用しているので、結果的に何も分からぬままスクラムマスターにもなりました。

ポジションが変わったことで、自分に求められるスキルも変わりました。これまでは早く、正確に。美しく保守可能なコードをガンガン書ける存在であることを求められていたのですが、今ではチームのメンバーを導き、スムーズに開発を継続させなければいけません。

メンタリングの方法や、1on1のやり方、タスクの進捗管理スクラムスクラムマスターとは何か...などインプットの内容も大きく変わり、代わりに技術系の情報のインプット量が減ってしまいました。

ブログのネタの多くが技術系の実際にコードを書くものだったので、そもそも記事にしようというモチベーションがありませんでした。

ストレス過多で肌トラブルに

エンジニアになったのは、多くの人とコミュニケーションをとる必要がない、営業さんのように他社に多く訪問に行きたくない...etcといった理由もありました。喋るのは下手ではないと思うのですが、積極的に自分から話しかけるというのは苦手です。

そういったストレスを回避するためにエンジニアなったはず...なのですが、開発チームのリーダーとなったことで、コミュニケーションによるストレスが多く発生するようになりました。個人的に一番きつかったのは、zoomでの打ち合わせや、タスクチェックなどで「問題ないか」という問いかけに対して、シーン...となることです。
「俺だって好きで進行やってない!」と何度思ったことか。

上記が原因なのかわかりませんが、肌が荒れました。元々、肌が弱くて、ストレス過多になると体に湿疹が出たりすることはありましたが、顔にだけは症状が出たことがなかったです。しかし、今年の1月頃に初めて顔に症状が出てしまい、大きなストレスになりました。

(※少しずつ回復しており、かなり楽になりました。 )

でもブログをまた頑張る

ブログを書かなくなったことで明らかに自分の技術のインプットの成長スピードが遅くなりました。それにコードを書く機会が減って、自分の開発者としての価値も下がっているかと危機感を感じています。リーダーはリーダーで面白いところはあるのですが、まだ開発者として現役で現場にいたいので、技術に対しての気持ちを途切れないようにしないといけないと思いました。

なんだかんだで、PVは少ないブログではありますが2年以上続けられているわけなので、自分にあっているのだと思います。なので、改めて頻度を高められるようにブログの執筆を頑張ってみようと思います。

まずは一週間に1記事を書く感覚を取り戻していこう!!

【Elixir】無名関数で気軽に再帰的にメッセージの送受信を実装する

Elixirでの並行処理

Elixirでは並行処理を行うためにアクターモデルに従ってプロセス間同士でメッセージを送受(メッセージパッシング)し合います。
アクターモデルとは並行処理を効率よく実装するための手法の一つです。Elixirにおいては、アクター(軽量プロセス)はアクター(軽量プロセス)を生成することが出来ます。また、全てのアクターはメッセージボックスを持ち、それぞれがメッセージの送受信を行うことができます。

全ての値は暗黙的に変更されることはなく、メッセージの送受の結果としてだけ値の変更が許容されます。逆に言えば、メッセージの送受以外では値の変更はされることはありません。そのため、データ不整合の問題や、ロック(排他処理)を明示的に実装する必要がありません(任意の処理が終わるまで待つということはあるでしょう)。

ids = [1, 2, 3, 4]

=> message: push 5

ids = [1, 2, 3, 4, 5]

=> message: pop

ids = [1, 2, 3, 4]

Elixirでのメッセージパッシングの実装については過去に記事にしておりますので、合わせてご覧ください。

www.okb-shelf.work

今回は、メッセージパッシングを実装する上でメッセージを何度も受信して、処理を行うという再帰的なメッセージパッシングを無名関数でサクッと記述する方法についてまとめております。

通常の再帰的なメッセージ受信の記述

こんな感じで、気軽に実装しようとすると、わざわざmoduleを使って実装する必要があり、個人的にはボリューミーだなぁと思ってしまいます。とはいえ、可読性はかなり良いですね。素直にmoduleを使って実装するのが一番だなと改めて感じさせられました。

def receive_message([]), do: {:ok, []}
  def receive_message([h | t]) do
    receive do
      {:POP, _pid} ->
        IO.puts("[Info]: #{h} was popped")
        receive_message(t)
      _ -> exit(:safety)
  end
end

結果確認のため、簡単にプロセスの起動と、メッセージの送信部分を関数化してmoduleに内包しました。

defmodule Recursive.Reciver do
  def process do
    pid = Enum.map(1..10, &(&1)) |> spawn_receiver()
    Enum.each(1..10, fn _ ->
      :timer.sleep(1000)
      send(pid, {:POP, self()})
    end)
  end
  def spawn_receiver(lst) do
    spawn(fn -> receive_message(lst) end)
  end
  def receive_message([]), do: {:ok, []}
  def receive_message([h | t]) do
    receive do
      {:POP, _pid} ->
        IO.puts("[Info]: #{h} was popped")
        receive_message(t)
      _ -> exit(:safety)
    end
  end
end

実行結果

iex(5)> Recursive.Reciver.process
[Info]: 1 was popped
[Info]: 2 was popped
[Info]: 3 was popped
[Info]: 4 was popped
[Info]: 5 was popped
[Info]: 6 was popped
[Info]: 7 was popped
[Info]: 8 was popped
[Info]: 9 was popped
[Info]: 10 was popped
:ok

無名関数を使った再帰的なメッセージ受信の記述

以下の記事で紹介した無名関数の再起処理のテクニックを用いて、こんな感じで無名関数だけでmoduleを使わずに記述することが出来ました。

www.okb-shelf.work

(あれ、moduleの方がいいんじゃないか...??)
(もはやロマン)

receiver = fn lst ->
  recurcive_ = fn lst_, own ->
    receive do
      {:POP, _pid} ->
        [h | t] = lst_
        IO.puts("[Info]: #{h} was popped")
        own.(t, own)
      _ -> exit(:safety)
    end
  end
  recurcive_.(lst, recurcive_)
end

無名関数は自身のスコープを持っておらず、自己参照することは出来ないので、引数に自分自身を渡してあげます。こうすることで無名関数から自分自身を呼び出し再帰処理を行うことが可能になります。あとは先ほどと同じように再帰的なメッセージの受信の処理を記述してあげれば良いですね。

# 実行系
process = fn ->
  lst = Enum.map(1..10, &(&1))
  selecter_pid = spawn(fn -> receiver.(lst) end)
  Enum.each(1..10, fn _ ->
    :timer.sleep(1000)
    send(selecter_pid, {:POP, self()})
  end)
end

実行結果

iex(8)> process.()
[Info]: 1 was popped
[Info]: 2 was popped
[Info]: 3 was popped
[Info]: 4 was popped
[Info]: 5 was popped
[Info]: 6 was popped
[Info]: 7 was popped
[Info]: 8 was popped
[Info]: 9 was popped
[Info]: 10 was popped
:ok

メッセージの種類や、データの量が多くなってくるようであれば、素直にTaskAgentGenServerを使って実装した方が楽になっていきます。

参考文献