Ruby | 2週間で身につく、アプリを作りながら学ぶRubyの基本 - 9日目

Ruby Ruby
スポンサーリンク

9日目のゴールとテーマ

9日目のテーマは「名簿アプリを“きれいに整える”:リファクタリングとエラー処理の強化」です。
ここまでで、機能としてはかなり充実した名簿アプリができています。

今日はそこから一歩進めて、
コードの整理(リファクタリング)と、
「エラーが起きても落ちにくい書き方」を体験していきます。

機能を増やす日というより、
今あるものを“育て直す日”だと思ってください。


いまのアプリの状態を言葉で整理する

役割が少しずつ増えてきた現状

ここまでで、あなたのコードにはだいたい次のような要素が並んでいます。

Person クラスがあり、名前・年齢・趣味・自己紹介文・保存用文字列などを扱っている。
people 配列があり、Person の集まりとして名簿を表している。
メニュー表示、メニュー入力、各メニューの処理(全員表示、追加、検索、並べ替えなど)がある。
ファイルへの保存と読み込みの処理がある。

一つ一つは理解できていると思いますが、
ファイル全体を眺めると「いろんなものが同じ場所に詰め込まれている」感じがしてきませんか。

ここで初めて、「役割ごとに整理する」という視点を持ってみます。


リファクタリングとは何かをイメージでつかむ

「動きを変えずに、形だけを良くする」

リファクタリングという言葉はよく出てきますが、
本質はとてもシンプルです。

動作(機能)は変えない。
でも、コードの形を変えて、読みやすく、直しやすくする。

つまり、「外から見たら同じように動くけど、中身の配線をきれいにする」作業です。

今日やるのは、まさにこれです。
新機能を足す前に、一度“掃除”をしておくイメージです。


アプリ全体を「Appクラス」にまとめる発想

手続きの寄せ集めから「アプリという1つのもの」へ

今のコードは、おそらくこういう感じになっています。

Person クラスの定義がある。
いくつかのメソッド(build_person_from_input、show_menu、handle_〜、save_people、load_people など)が並んでいる。
最後に people = load_people から始まるメインループがある。

これを、「App というクラス」にまとめてしまう、という考え方があります。

アプリ全体を表す App クラスを作り、
その中に people やメニュー処理、保存処理などを閉じ込める。

そうすると、最終的な起動コードはこうなります。

app = App.new
app.run
Ruby

この二行だけで、「アプリを起動する」という意味が伝わるようになります。


Appクラスの骨組みを作る

まずは最小限の形から

App クラスの最初の形は、とてもシンプルで構いません。

class App
  def initialize
    @people = load_people
  end

  def run
    loop do
      show_menu
      choice = read_menu_number

      if choice == 0
        puts "アプリを終了します。"
        save_people(@people)
        break
      elsif choice == 1
        handle_show_all(@people)
      elsif choice == 2
        handle_add_person(@people)
      elsif choice == 3
        handle_show_under_20(@people)
      elsif choice == 4
        handle_search_by_name(@people)
      elsif choice == 5
        handle_show_sorted_by_age(@people)
      else
        puts "不正な番号です。もう一度入力してください。"
      end
    end
  end
end
Ruby

ここでの重要ポイントを深掘りします。

initialize で @people を用意している。
@people は「このアプリが持っている名簿」です。
run メソッドが「アプリのメインループ」を表している。
今までトップレベルに書いていたメインループが、App の中に移動しただけです。

この時点では、load_people や save_people、show_menu などは
まだクラスの外にあるかもしれませんが、
まずは「アプリの入口を App#run にする」という一歩が大事です。


メソッドをAppの中に“引っ越し”させる

外に散らばっている関数をまとめていく

次にやるのは、今までバラバラに定義していたメソッドを
少しずつ App クラスの中に移していく作業です。

例えば、show_menu はこうなります。

class App
  def show_menu
    puts "========================"
    puts "名簿アプリ メニュー"
    puts "1: 全員の自己紹介を表示"
    puts "2: 新しい人を追加"
    puts "3: 20歳未満の人だけ表示"
    puts "4: 名前で検索して表示"
    puts "5: 年齢の若い順に全員を表示"
    puts "0: 終了"
    puts "番号を入力してください:"
  end
end
Ruby

read_menu_number も App の中に入れます。

class App
  def read_menu_number
    loop do
      input = gets
      return 0 if input.nil?

      text = input.chomp

      if text.match?(/\A[0-9]+\z/)
        return text.to_i
      else
        puts "数字で入力してください。もう一度どうぞ:"
      end
    end
  end
end
Ruby

handle_〜 系のメソッドも同様に、App の中に移していきます。

ここでの深掘りポイントは、「@people をどう扱うか」です。

今までは handle_show_all(people) のように、
配列を引数で渡していました。

App の中に入れるなら、
引数をやめて @people を直接使う、という選択肢もあります。

例えば、こう書き換えられます。

class App
  def handle_show_all
    if @people.empty?
      puts "まだ登録されている人がいません。"
      return
    end

    puts "========================"
    puts "全員の自己紹介を表示します。"

    @people.each_with_index do |person, idx|
      puts "------------------------"
      puts "#{idx + 1}人目:"
      puts person.introduction
    end
  end
end
Ruby

そして run の中では、こう呼びます。

elsif choice == 1
  handle_show_all
Ruby

このように、「アプリが持っている状態(@people)」を
クラスの中で直接扱うようにすると、
引数の受け渡しが減って、コードがすっきりしていきます。


エラー処理の基本「begin…rescue」を体験する

例外が起きるとどうなるか

Ruby では、何か問題が起きたときに「例外(Exception)」が発生します。

例えば、存在しないファイルを読み込もうとしたり、
数字に変換できない文字列を無理に数値として扱おうとしたりすると、
プログラムがエラーで止まってしまいます。

今までは、File.exist? などで事前にチェックしていましたが、
それでも予期せぬエラーが起きることはあります。

そこで登場するのが begin…rescue です。

begin…rescueの最小例

まずはシンプルな例から。

begin
  puts "数字を入力してください:"
  text = gets.chomp
  number = Integer(text)
  puts "あなたが入力した数字は #{number} です。"
rescue ArgumentError
  puts "数字として解釈できませんでした。"
end
Ruby

ここでの重要ポイントを丁寧に見ていきます。

Integer(text) は、text を整数に変換しようとします。
to_i と違って、「変換できない場合は 0 にする」のではなく、
ArgumentError という例外を投げます。

begin 〜 rescue 〜 end で囲まれた中で例外が起きると、
その瞬間に rescue に飛びます。
rescue の中で「エラーだったときの処理」を書けます。

この例では、「数字として解釈できませんでした。」と表示して終わりです。


ファイル読み込みに例外処理を足してみる

load_peopleを少しだけ強くする

8日目で書いた load_people は、File.exist? で存在チェックをしていました。
ここに、例外処理を足してみます。

def load_people
  people = []

  unless File.exist?(DATA_FILE)
    puts "保存ファイルがまだありません。(初回起動かもしれません)"
    return people
  end

  begin
    File.open(DATA_FILE, "r") do |file|
      file.each_line do |line|
        next if line.strip == ""
        person = person_from_csv_line(line)
        people << person
      end
    end
    puts "名簿をファイルから読み込みました。(#{people.length}人)"
  rescue Errno::EACCES
    puts "ファイルにアクセスできませんでした。(権限エラー)"
  rescue => e
    puts "ファイル読み込み中に予期せぬエラーが発生しました。"
    puts "エラー内容: #{e.class} #{e.message}"
  end

  people
end
Ruby

ここで深掘りしたいポイントは三つです。

一つ目は、特定のエラークラスを rescue していることです。
Errno::EACCES は「権限がなくてファイルを開けない」場合のエラーです。
こういう「よくあるエラー」に対して、
ユーザーに分かりやすいメッセージを出せます。

二つ目は、最後の rescue => e です。
これは「どんな種類の例外でも受け取る」ための書き方です。
e.class や e.message を表示すれば、
開発中に「何が起きたのか」を知る手がかりになります。

三つ目は、「エラーが起きても people を返している」ことです。
たとえ読み込みに失敗しても、アプリ全体が落ちるのではなく、
「空の名簿で起動する」という選択肢を取れます。


「落ちないアプリ」に近づけるという感覚

すべてを完璧に守る必要はない

現実のアプリでも、
すべてのエラーを完全に防ぐことはできません。

大事なのは、「エラーが起きたときにどう振る舞うか」です。

ユーザーに何も言わずに落ちるのか。
「こういう理由で、この機能は今使えません」と伝えるのか。
別の安全な状態に戻るのか。

9日目では、その入り口として、

入力のチェックを少し丁寧にする。
ファイル読み込みに begin…rescue を足す。
App クラスの中で「アプリ全体の流れ」を握る。

こういったことを体験してもらいました。


9日目のまとめ

今日の大事なポイントを整理します。

リファクタリングは「動きを変えずに形だけを良くする」作業である。
App クラスを作り、run メソッドにメインループを閉じ込めることで、
「アプリ全体」を1つのオブジェクトとして扱えるようになる。
@people のようなインスタンス変数を使うことで、
引数の受け渡しを減らし、コードの見通しを良くできる。
begin…rescue を使うと、「エラーが起きたときの振る舞い」を自分で決められる。
ファイル読み込みや数値変換など、“壊れやすい場所”に例外処理を足すと、
アプリが「落ちにくい」方向に一歩進む。

ここまで来ると、
「とりあえず動くコード」から
「育てていけるコード」に変わり始めています。

次のステップでは、
この App クラスをさらに育てて、
別の種類のアプリ(例えばタスク管理や簡易家計簿)にも
同じ考え方を応用していくことができます。

もし「この名簿アプリを別のテーマに置き換えてみたい」と感じているなら、
それはもう、あなたの中で“自分のアプリを設計するモード”が動き始めているサインです。

タイトルとURLをコピーしました