2022年の抱負
はじめに
こんにちは! フィヨルドブートキャンプで学習中のwataです。
最初のブログを投稿してから日が経ちすぎたので、ブログを書こうかなと思い立ったのですが、ネタが中々思いつかないこの頃です。😓
私事ですが、今年の3月末に新卒で入社したSES企業を退社しました。
現在は、転職に向けてフィヨルドブートキャンプ(以下、FBC)でフルコミット中です。
3月に退職したこともあり、4、5月は自分にとって気持ちの切り替わりのタイミングとなり、新年を迎えたような気分になっています。
そこで、2022年の抱負を今回の記事にしようと思います。
目次
目標
私は、Web系企業への転職を本気で目指すために、退職して勉強一本に絞ることにしました。
在職中も、転職に向けた勉強をしていましたが、なかなか仕事と勉強の両立は難しかったです。 というのも、案件の納期が迫ると残業時間が増えるため、勉強しようという気が起きず… 勉強が疎かになっていました。
しかし、現在は無職です。悠長なことは言ってられません! 転職活動は12月までと期限を設けました。
そこで、12月までに転職を成功させるために、目標を設けました。 自分が立てた目標は多いので、5つ紹介します。
真面目に就職活動すること
当たり前かもしれませんが。😅 私が新卒で就職活動をしたときは、この基本ができてなかったように思います。
新卒のときも、Web系企業への就職を目標としていました。 しかし、漠然とした憧れだけで、自己分析や面接対策等を怠った結果、就職活動は大失敗に終わりました。 とにかく本気度が足りなかったです。
転職活動は、この反省を踏まえ、「ダラダラと取り組まない」「一つ一つ真剣に取り組む」よう心がけます。
プロジェクトの一貫として取り組むこと
「プロジェクトの一環として取り組む」とは、「計画、実行、振り返り」を短期的に繰り返すことです。
以前から就職活動のために、直向きに学習をしてましたが、行き当たりばったりの行動は不味いなと、考えを改めました。 就職までの道のりでは、思いの外やることが多いです。例えば、私の場合だと以下のようなことが挙げられます。
上記を1時間単位のタスクに分解すると、相当な時間が算出されると思います。 そもそも、作業時間を正確に見積もることは難しいですよね。 さらに、タスク分解をしても、「取り返しのつかない漏れ」があるかもしれないです。
つまり、就職活動では不確実な要素が多いと言えそうです。 何とか策を練らないと新卒時と同じ二の舞を踏みそうです... そうならないように、就職活動を「プロジェクト」として意識しながら進めていきます。
アウトプットの頻度を上げること
ここでの「アウトプット」はブログを投稿することとします。
約1年前にアウトプット用のブログを開設しましたが、投稿数は1本だけです... 「はじめに」でも述べましたが、何を書くべきかが悩ましいです。
技術のアウトプットに関しては、FBCのメンターである伊藤さんのこちらの記事が参考になりました。 blog.jnito.com
スキルアップに役立つアウトプットは、「自分の経験がベースになっていて、なおかつ、自分でもよくわかっていない点を詳しく調べた上で書いたアウトプット」です。
コードを書いてたり、学習をしてると日頃疑問に思うことは多そうです。 そこで、自分の興味・関心事を中心にネタを探してみようかなと思います。 投稿頻度の目標は月1本です。
コミュニケーション(横の繋がりを増やすこと)
コミュニケーションも細かく見れば色々ありますが、特に人間関係です。 人間関係は、根っからの内向的な性格が起因して大きな課題の1つになってます。
FBCはよく「スクールというよりコミュニティ」と呼ばれてますが、1年入会してもそこまで馴染めてないです。 大勢の輪に入るのが苦手で、コミュニティの方達と会話したことほぼ無いです。
現状の改善策は、場数を踏む、つまり人との関わりを多く持つしかないかなぁと考えてます。 ただ、いきなり大勢の中で会話をして、交流を深めるのはハードルがかなり高いです。 なので、TDD(テスト駆動開発)みたくスモールステップで着実に前進するのが得策と思ってます。
例えば、ミートアップのラジオ参加や輪読会に参加し、スタンプやテキストでアクションすることです。 最近では、朝に「今日やること報告+雑談」を軽く行う「目覚まし会」にも参加してます。
まずは、少しずつ馴染めるようにしたいです。
強みを伸ばすこと
エンジニアチームの一員として活躍するために、今のうちから強みを磨きたいです。 私の中で、自分の強みとは「自分の興味・関心のあること」と定義しています。
具体的には下記の項目になります。
企業が求めていることと、強みがマッチしているかは正直まだ分からないです。 とりあえずやってみる精神で行こうと思います。
まとめ
2022年の抱負を簡単に述べました。 具体的な目標達成へのプロセスは別途ブログに投稿しようと思います。
目標達成に向けて、やらないといけないこと、やりたいこと、やるべきことが多いです。 悔いの残らないように、1つ1つを丁寧に、精一杯取り組んでいきたいと思います。💪
リファクタリングの奨め
はじめに
こんにちは〜! フィヨルドブートキャンプ現役生のwataと申します。
これは、「フィヨルドブートキャンプ Advent Calendar 2021」の18日目の記事です。
フィヨルドブートキャンプ Part 1 Advent Calendar 2021 - Adventar
フィヨルドブートキャンプ Part 2 Advent Calendar 2021 - Adventar
軽い自己紹介ですが、私は現在、新卒で入社したSESの企業でプログラマーとして働いており、入社3年目になります。
フィヨルドブートキャンプに入会して約半年、全くブログを公開してませんでした(汗)。Advent Calendarをきっかけに重い腰を上げてエントリーしてみました!
初投稿なので、温かい目で見守ってください笑🙇♂️
本記事では、リファクタリングを知らない人、リファクタリングに抵抗を感じる人を対象に、自分が日頃実践しているマーティン・ファウラー著のリファクタリング本の手法を紹介しつつ、リファクタリングの魅力を伝えられればと思います!
※以降の章では、「リファクタリング本」のことを「リファクタ本」と省略することにします。
※本記事で説明するコードは、フィヨルドブートキャンプのプラクティスであるFizzBuzz問題
、カレンダープログラム
を取り扱います。
目次
リファクタリングとは
まずはリファクタリングの定義の説明をします。リファクタリング本で次のように述べています。
外部から見たときの振る舞いを保ちつつ、 理解や修正が簡単になるように、 ソフトウェアの内部構造を変化させること。
MartinFowler. リファクタリング 既存のコードを安全に改善する(第2版) (p.45). Kindle 版.
外部の振る舞いとは、アプリケーションの動きのことです。内部機能とは、外部の振る舞いを満たすためのロジック、処理の詳細になります。
リファクタリングの肝は改善することにあります。例としてFizzBuzz問題を取り扱います。最初に、下記のような内部機能となるコードを書いて、FizzBuzz問題の振る舞いを実現したとします。
def FizzBuzzProblem(number) if (number % 15).zero? puts 'FizzBuzz' elseif (number % 3).zero? puts 'Fizz' elseif (number % 5).zero? puts 'Buzz' else puts number end end
次に、リファクタリングによって改善したコードは、下記のようになります。
def FizzBuzzProblem(number) if FizzBuzz?(number) puts 'FizzBuzz' elseif Fizz?(number) puts 'Fizz' elseif Buzz?(number) puts 'Buzz' else puts number end end def FizzBuzz?(number) Fizz?(number) && Buzz?(number) end def Fizz?(number) (number % 3).zero? end def Buzz?(number) (number % 5).zero? end
改善前後を比較してみます。改善後のコードは、メソッドを増やしたため全体のコード量がふえました。しかし、条件のロジックをメソッドで切り分けることで、条件に名前を付けることができました。 名前を付けることで、処理の詳細を見なくても概要を把握が容易になります。
また、FizzBuzz問題の要件が変更されても、すぐに該当箇所を把握できる、変更内容が他の処理に影響しない構造になっています。
しかし、改善後のコードに振る舞いの変化はありません。
理解しづらい、変更しづらいコードは色々と辛い
FizzBuzz問題では、リファクタリングの旨味を薄く感じるかも知れないです。少し長めのコードを例にしたいと思います。リファクタリングすべきコードは以下のパターンがあります。
- メソッドの中身が膨大
- 変数名、関数名とコードの表現が不一致
メソッドの中身が膨大である特徴の1つとして、「1つのメソッドの中に、別のメソッドを呼び出さず、そのまま処理を書く」ことがあります。
カレンダープラクティスのコードを例にします。2021年の12月のカレンダーを表示したプログラムで、外部の振る舞いは下記の3つです。
- コンソールの1行目に年と月を表示する
- コンソールの2行目に曜日を表示する
- コンソールの3行目以降に各週ごとの日を表示する
def cal year = 2021 month = 12 # 12月の週を配列の要素とする。 # さらに各週ごとの日を要素とする。曜日に紐づく日がない場合はnil。 days = [ [nil, nil, nil, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16, 17, 18], [19, 20, 21, 22, 23, 24, 25], [26, 27, 28, 29, 30, 31, nil] ] # 外部の振る舞い1。カレンダーを描画 puts "#{month}月 #{year}".center(20) # 外部の振る舞い2。曜日を描画 displayed_day_of_week = %w[日 月 火 水 木 金 土] sep = ' ' puts displayed_day_of_week.join(sep) # 外部の振る舞い3。各週の日を描画 days.each do |week| displayed_week = week.map do |day| # 曜日列に日付がない場合は空白表示 next ' ' if day.nil? # 日付の文字数が1文字の場合は空白を追加して幅を調整 padding_width = day < 10 ? 2 : 0 day.to_s.rjust(padding_width) end sep = ' ' puts displayed_week.join(sep) end end cal
各外部の振る舞いの開始位置に、振る舞いのコメントを書いてます。コメントがあるため、上から読めば、各外部の振る舞いが理解可能です。しかし、コードの理解、機能の追加、不具合の修正を行う度に、以下のことを意識する必要があります。
- 各処理の範囲がどこまでか。
- 明確なくくりがないので、視覚的にわかりづらい。
- 各処理の振る舞い
が何を表すのか。
- 概要を表現したコメントがない場合、コードの詳細を追わなければならない。
- 経験的に、コメントはメンテされにくいので、概要を記載したコメントと処理の意味に乖離が起きやすい。
- 各処理で使用した変数が、他の処理で変更されてないか。
- 変数のスコープが1つの処理の前後まで及ぼすので、影響している可能性がある。
これらの労力は、1つのメソッドの中にあるコード量に比例します。カレンダーのコードは、先程のFizzBuzz問題のコードと比較すると、注意する労力をより一層要します。
また、2つ目のパターンの変数名、メソッド名の名前が不一致だと、せっかく付けた名前も無駄になります。結局、処理の詳細を見ないと、誤解なく正確に理解することが難しいからです。
私が今まで職場で見たコード(私自身含めて)も、このパターンは多かったです。バグの調査、コードの修正や、コードで仕様把握等の作業過程で、コードを理解するのに時間がかかり、苦労した思い出があります😭
リファクタリングの紹介
カレンダープログラムのサンプルコードを例にして、リファクタ本
にある2つの手法を紹介していきたいと思います。
テストコードを書く
リファクタリングをする準備として、テストコードを書きます。 テストコードは、メソッドの振る舞いが期待通りに動くか確認するための用途になります。 リファクタリングをする上で、テストコードを書くメリットは以下が挙げられます。
- 修正中にバグを素早く検知することができる
- 少しずつリファクタリングを進めることができる
テストコードがない場合、プログラムが正常に動作する検証方法は、プログラムの実行が必要です。プログラムの実行は全体を通した確認ができても、部分的に正しいかを詳細に見ることはできません。
私が経験した現場では、テストコードがなかったので、修正してもデグレが多発しました。そのため、今までテストした項目も最初からテストすることがありました...
片やテストコードは、修正したコードが原因で不具合が発生すれば、ピンポイントでかつ素早く通知します。安心してプログラムの修正に取り掛かることが可能です。
先程のカレンダープログラムを構成する外部の振る舞い3
を対象に、テストコードを書いてみます。
テストコードの対象を見つける手順は、メソッドの抽出
で説明します。
各週ごとの日付を表示する処理がありますが、この処理の入力と出力を考えます。
- 入力:1週間の日を要素とした1次元配列。
- その週の日がない場合、要素はnil。
- 出力:1週間の日を空白区切りの文字列で出力。
- 日付表示の最大幅は2。
- 日付が1文字の場合は右詰め。
- 要素がnilの場合は空白表示。
上記の入力と出力を元に、以下のようなテストコードを書きます。 1例として入力は、最初の週にします。
テストコード
require 'minitest/autorun' require_relative './cal' class CalTest < Minitest::Test def test_get_week # 入力 input = [nil, nil, nil, 1, 2, 3, 4] # 期待する出力 expected = ' 1 2 3 4' # 検証。 assert expected, get_week(input) end end
テストコードにMinitestを使用しています。テストコードのディレクトリは、cal.rbと同じです。
リファクタリングの最低限の準備はこれだけです。
次はリファクタリングに入ります。
メソッドの抽出
長い処理の中には、細分化できる塊が多く存在します。この塊には、それぞれの役割があり独立できる可能性があります。意味のある塊をコメントで表現するのではなく、メソッドとして抽出することは以下のメリットがあります。
- 塊の範囲が視覚的にわかりやすい。
- リファクタリングが容易にできる。
- 関数名の変更、内部機能の変更など。
- 処理を閉じることで、他の関数からの影響を受けにくい。
メソッドの抽出は、大まかに以下の方法で実現できます。リファクタ本
では、グローバルなパラメータを参照している場合など、複雑な状況に対応した手順が紹介されています。
- 意味のある塊(処理)を探す。
- 抽出用のメソッドを用意する。
- 手順1で探した処理をコピーし、抽出用のメソッドにペーストする。
- コピー元の処理を削除して抽出メソッドを呼び出す。
上記の手順を見るとそこまで複雑なことはしてないです。
外部の振る舞い3
を対象にメソッドの抽出を行います。
まずは手順1の「意味のある塊」を探します。
この振る舞いを実現するために、内部機能では、1週間ごとの日を表示する繰り返し処理を行っています。1週間ごとの日を表示する処理は意味のある塊なので、抽出対象となります。
該当箇所は下記になります。
# 日付をカレンダーのフォーマットに変更 displayed_week = week.map do |day| # 曜日列に日付がない場合は空白表示 next ' ' if day.nil? # 日付の文字数が1文字の場合は空白を追加して幅を調整 width = 2 day.to_s.rjust(width) end sep = ' ' puts displayed_week.join(sep)
putsは、引数を標準出力に出力するメソッドなので、これを塊の範囲内にすると、返り値がないメソッドになってしまいます。テストコードでは返り値を期待結果と比較するので、putsは除外します。
次に手順2です。
抽出用のメソッドを用意します。メソッド名の検討は後ほど考えて、暫定的な名前を付けます。
def get_week end
手順3です。 コピーした処理を抽出用メソッドにコピーします。putsは除外します。
def get_week # 日付をカレンダーのフォーマットに変更 displayed_week = week.map do |day| # 曜日列に日付がない場合は空白表示 next ' ' if day.nil? # 日付の文字数が1文字の場合は空白を追加して幅を調整 width = 2 day.to_s.rjust(width) end sep = ' ' displayed_week.join(sep) end
この状態でテストコードを実行してみると失敗します。メソッドの引数がないからです。
コピー元のコードでは、変数week
をパラメータとして使用しているので、week
を引数として
追加します。
def get_week(week) # 日付をカレンダーのフォーマットに変更 displayed_week = week.map do |day| # 曜日列に日付がない場合は空白表示 next ' ' if day.nil? # 日付の文字数が1文字の場合は空白を追加して幅を調整 width = 2 day.to_s.rjust(width) end sep = ' ' displayed_week.join(sep) end
最後にコピー元のメソッドを削除してメソッドを呼び出します。メソッドの引数があることに注意します。
def cal # ... 省略 # 各週の日にちを描画 days.each do |week| displayed_week = get_week(week) puts displayed_week end end
メソッドの抽出が完了しました。
リファクタリング前後で比べていかがでしょうか。リファクタリング後のコードは、処理の概要を把握しやすくなったと思います。
このコードを見るとさらに塊があります。繰り返し処理をしている処理です。これもメソッドの抽出ができます。
※テストコードは省略します。
def cal # ... 省略 print_days(days) end def print_days(days) days.each do |week| displayed_week = get_week(week) puts displayed_week end end
次に、抽出したメソッド名をリファクタリングしていきます。
メソッド名の修正
メソッドの抽出
で作られたメソッドにより、長い処理を意味のある塊として切り分けをしました。しかし、意味のある塊を表現する名前が不適切な場合もあります。名前と処理の表現に乖離があると以下のデメリットがあります。
- 処理の内容を誤解してしまう
- 理解するのにコストがかかる
名前が不適切だと、結局は処理の詳細を追わなければなりません。
名前の重要性についての詳細と、良い名前の付け方は、WEB+DB PRESS Vol.110の名前付け大全
記事が参考になります。
名前を修正するリファクタリング手順について、リファクタ本
では移行的手順が紹介されています。
移行的手順は主に以下のステップで行います。
- リネームの対象となるメソッドの処理全体に、
メソッドの抽出
を施す。 - 古いメソッド名で呼び出している箇所を、新しいメソッド名にリネームする。
メソッドの抽出
でリファクタリングした外部の振る舞い3
を対象に、メソッド名の修正を行います。
この塊の処理は、表示するカレンダー用に1週間の日付を文字列で返しています。
そこで、修正後の名前をdisplayed_week
とします。Rubyでは命名にget
、set
をつけない慣習なので、getは省略します。
手順1です。
メソッドの抽出を行います。抽出する範囲は、メソッドの処理全体になります。
def get_week(week) displayed_week(week) end def displayed_week(week) # 日付をカレンダーのフォーマットに変更 displayed_week = week.map do |day| # 曜日列に日付がない場合は空白表示 next ' ' if day.nil? # 日付の文字数が1文字の場合は空白を追加して幅を調整 width = 2 day.to_s.rjust(width) end sep = ' ' displayed_week.join(sep) end
次に手順2です。
テストコード、呼び出し元のコードを1つずつ新しいメソッドで置き換えます。
def cal # ... 省略 print_days(days) end def print_days(days) days.each do |week| puts displayed_week(week) end end
この方法だと、単純にメソッド名を一括置換する方法と比較して、安全に変更することができます。(IDEのリネーム機能は例外かもしれません)。 途中でテストコードを実行して、正常動作するか確認ができます。 また、途中でリネームの作業を止めてもコードは動くので安心です。
print_days
のメソッド名も不適切なので、同様にリファクタリングしていきます。
print_days
は各週の日付を1行ごとにputs
メソッドでコンソールに表示しているので
display_weeks
とします。
リファクタリングの紹介まとめ
テストコードを作成し、メソッドの抽出とメソッド名のリファクタリングを適用しました。
他の箇所で上記の方法を適用できたり、変数名の修正ができるので、最終的に修正したコードが下記になります。
def cal year = 2021 month = 12 # 12月の週を配列の要素とする。 # さらに各週ごとの日を要素とする。曜日に紐づく日がない場合はnil。 # 曜日のはじめは日曜。 weeks_of_one_month = [ [nil, nil, nil, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16, 17, 18], [19, 20, 21, 22, 23, 24, 25], [26, 27, 28, 29, 30, 31, nil] ] # 外部の振る舞い1。 puts displayed_year_and_month(year, month) # 外部の振る舞い2。 puts displayed_day_of_week(%w[日 月 火 水 木 金 土]) # 外部の振る舞い3。 display_weeks(weeks_of_one_month) end def displayed_year_and_month(year, month) "#{month}月 #{year}".center(20) end def displayed_day_of_week(day_of_week) day_of_week = %w[日 月 火 水 木 金 土] sep = ' ' day_of_week.join(sep) end def display_weeks(weeks) weeks.each do |week| puts displayed_week(week) end end def displayed_week(week) # 日付をカレンダーのフォーマットに変更 displayed_days = week.map do |day| # 曜日列に日付がない場合は空白表示 next ' ' if day.nil? # 日付の文字数が1文字の場合は空白を追加して幅を調整 width = 2 day.to_s.rjust(width) end sep = ' ' displayed_days.join(sep) end cal
今回紹介した方法は、意外にも簡単にできると思います。実際、私がリファクタ本を実践する前は難しいものだと感じてましたが、プラクティスでもすぐに実践できました。
冒頭のリファクタリングとは
で、改善することがリファクタリングの肝だと言いましたが、もう1つ肝があります。
リファクタ本
では、リファクタリングについて以下のことも述べています。
リファクタリングは振る舞いを保ちつつ小さなステップを適用していくことであり、ステップを積み重ねていくことで大きな変化をもたらしていくものなのです。ここのリファクタリングでは非常に小さいステップ、またはそれらの組み合わせでできています。
MartinFowler. リファクタリング 既存のコードを安全に改善する(第2版) (p.46). Kindle 版.
つまり、リファクタリングは、いきなり大規模にコードを修正することではなく、小さな範囲から始めることになります。リファクタリングは、熟練者だけができる方法論ではななく、誰でも簡単に始められるものです。
プラクティスで実践してみての所感
私は、lsコマンドを作る
までリファクタ本
を実践してきました。
実践を通して気づいたことは、「リファクタリングは良い循環を与えてくれる」ことです。
具体例として、下記のような、1度改善をするとさらに改善できそうなポイントを自然と見つけられる機会が多くなりました。
- メソッドを抽出したら、他にもメソッドを抽出できないかを探る。
- メソッドを作るのでメソッド名を考えるようになる。
- 名前がしっくり来なければ、切り出す範囲を変えてメソッドの抽出を再度行う。
- 名前を考える際に正しい表現なのか調べる。
- ドメイン内ではどのように使われているか。
- 意味の似た単語の違いは何か調べる。
- 複数の変数名に同じ形容詞がつくと、データのまとまりにできないか考える。その際に、まとまりを表現する名前を考える
いきなり良い設計が思い浮かばずとも、リファクタリングを繰り返すことで、良い設計にすることも可能なのかなと感じてます。まさに、小さなステップで大きな変化
を体感できるようになりつつあります。
リファクタ本
では、今回紹介した方法以外にも多くの手法が紹介されています。
また、手法だけでなく、なぜその改善をすべきなのか、改善していくと得すること等も説明があるので、良いコードと悪いコードの特徴を知ることもできます。
まとめ
本記事では、リファクタリングのメリットの説明と、リファクタ本
にある手軽に始められる手法の紹介をしました。
私は全くの駆け出しですが、今回の記事を通して、「ちょっとリファクタリングしてみようかな」と思って貰えると幸いです。
かなり長いかつ拙い文章ですが、最後まで読んでいただきありがとうございました。🙏