プログラミングスクールのフィヨルドブートキャンプの提出物のレビューでよく指摘するシリーズ。

独自の構造や単語を考える前に、組み込みライブラリ標準添付ライブラリでよく使われている構造や単語(クラス名・メソッド名)で表現できないか考えよう。

独自の構造・単語は作っている人にとってはわかりやすいかもしれないが、それを使う他人にとってはわかりづらい。rubyプログラマーにおなじみの構造・単語でなるべく作ろう。

Bad:

class Status
  def self.make_stat(file)
    {
      username: self.username(file),
      size: self.size(file),
      basename: File.basename(file)
    }
  end

  def self.username(file)
    # ...
  end

  def self.size(file)
    # ...
  end
end

puts Status.make_stat(file) # => {username => 'komagata', :size => 1234, basename: 'ls.rb'}

Good:

class Status
  def initialize(file)
    @file = file
  end

  def to_h
    {
      username: username, 
      size: size, 
      basename: File.basename(@file)
    }
  end

  def username
    # ...
  end

  def size
    # ...
  end
end

puts Status.new(file).to_h # => {username => 'komagata', :size => 1234, basename: 'ls.rb'}

プログラミングスクールのフィヨルドブートキャンプの提出物のレビューでよく指摘するシリーズ。

メソッド名に同じ名詞が頻出する場合、その名詞をクラス名として抜き出すとスッキリすることが多い。特にそれらのメソッドが同じインスタンス変数を使ってる場合は尚更。

Bad:

class File
  attr_accessor :permission

  def open
    # ...
  end

  def check_permission
    # ...
  end

  def fetch_permission
    # ...
  end

  def permission_characters
    # ...
  end
end

Good:

class File
  def open(path)
    # ...
    @permission = Permission.new(file)
  end
end

class Permission
  def initialize(file)
    @file = file
  end

  def check
    # ...
  end

  def fetch
    # ...
  end

  def characters
    # ...
  end
end

フィヨルドブートキャンプでメンターとしてコードレビューをしています。自由な形式でプログラムを書く課題の場合、やはりオブジェクト指向プログラミングにみなさん苦戦しているようです。

特に、オブジェクト指向プログラミングのイメージが掴めないと、

「何をクラスメソッドにして何をインスタンスメソッドにすればいいのか」

がわかりません。(みんなここで詰まっている)

なので、これを説明するのはものすごく難しいんですが、挑戦してみます。

(クラスを定義する記法や使い方はRuby超入門などで読んだという方を前提とします。)

クラスとインスタンス

クラスインスタンス鋳型実体 だと考えてください。

鋳型とはこういうやつです。

Image from Gyazo

タイヤキの鋳型はタイヤキそのものとは別のものですが、その鋳型からできる全てのタイヤキの形を決めています。

Image from Gyazo

Person(人)クラスで考えてみます。

Person(人)というものは現実の世界にはいません。実際にいるのは駒形、町田といった人の実体です。哺乳類という動物がいないのと同じです。実際にいるのはポチやタマです。

Personにはname(名前)やage(年齢)といった属性がありますが、「そういう属性があるよ」ということは共通していますが、実際の名前はそれぞれ実体によって違います。

クラス変数とインスタンス変数

そうやって考えてみると、変数に関してはクラス変数(もしくは定数)とインスタンス変数のどちらにすべきか自然とわかると思います。

例えば「目の数」という属性はPersonに共通する内容なのでクラス変数(もしくは定数)がふさわしいです。「性別」という属性はインスタンスによって違うのでインスタンス変数がふさわしいです。

クラスメソッドとインスタンスメソッド

全てをクラス変数とクラスメソッドで処理するというのは手続き型プログラミングの世界です。

関数(メソッド)は極力引数の情報だけで処理が完結すべきというのは関数型プログラミングの世界です。

オブジェクト指向プログラミングでは、極力「インスタンス変数を使ってそのインスタンス自身に処理させる」というのが基本です。

例えばself_introduce(自己紹介する)というメソッドを作る場合、それぞれこうなります。

手続き型プログラミング:

class Person
  def self.name=(name)
    @@name = name
  end

  def self.self_introduce
    puts "I'm #{@@name}."
  end
end

Person.name = "komagata"
Person.self_introcude

関数型プログラミング:

def self_introduce(name)
  puts "I'm #{name}."
end

self_introduce("komagata")

オブジェクト指向プログラミング:

class Person
  def initialize(name)
    @name = name
  end

  def self_introcude
    puts "I'm #{name}."
  end
end

komagata = Person.new("komagata")
komagata.self_introduce

どれが良い悪いというのはないですが、オブジェクト指向プログラミングは最後のスタイルです。

極力、実体(インスタンス)が個別に持つ情報を利用してインスタンスメソッドを呼び出して処理する。そういう書き方が基本です。

例えば「人を探す」というメソッドがあった場合、(なんらかの人物が他の人物を探すとかでない限り)特定の実体には関係がないのでクラスメソッドが適しています。

class Person
  def self.find(name)
    # 探して新しいPersonインスタンスを返す
  end
end

実際は実装上の都合やテクニックが加わってくるのでこれに当てはまらない場合もありますがあくまで基本の考え方はこれです。

フィヨルドブートキャンプのカリキュラムでlsをrubyで実装するというのがあるんですが、パーミッションの八進表記を記号表記(symbolic notation)に変換する部分をどう実装するのがベターなのかなというのを悩んでおります。

rubyのFile::Statで取れるのは八進表記。

$ ruby -e 'printf "%o\n", open("./foo").stat.mode'
100644

これを記号表記 -rw-r--r--に変換したい。

このrとかwとかがどこで定義されているのかがわからなかった。

何か共通の定義があってrubyでも簡単に読み込めるならそれを使うべきだし、本物のlsでもプログラム中で個別に定義してるならruby内で自分も定義して使っちゃっていいのかなと考え、macのソースコードを調べてました。

twitterで@massoさんなどに助言をいただき調べたところ、Libcのstrmodeの中に書いてありました。(lsもstrmodeを使って表示している)

strmode.c

    /* usr */
    if (mode & S_IRUSR)
        *p++ = 'r';
    else
        *p++ = '-';
    if (mode & S_IWUSR)
        *p++ = 'w';
    else
        *p++ = '-';
    switch (mode & (S_IXUSR | S_ISUID)) {
    case 0:
        *p++ = '-';
        break;
    case S_IXUSR:
        *p++ = 'x';
        break;
    case S_ISUID:
        *p++ = 'S';
        break;
    case S_IXUSR | S_ISUID:
        *p++ = 's';
        break;
    }

chmodなどはこれを使ってなくて直接chmod.cに書いてあったりしました。

今のところrubyから簡単にstrmodeを使う方法もなさそうなのでrubyコードの中に自分で定義しちゃうのでいいのかなというのが現状です。

もし「こういう方法が一般的 or スマート」というのをご存知の方がいらっしゃったらtwitterの@komagataまで教えていただければありがたいです〜。

プログラミングスクールのフィヨルドブートキャンプの提出物のレビューでよく指摘するシリーズ。

Bad:

# frozen_string_literal: true

class Foo
  require "optparse"

  attr_reader :xxxx

  def initialize
    # xxxx
  end
end

Good:

# frozen_string_literal: true

require "optparse"

class Foo
  attr_reader :xxxx

  def initialize
    # xxxx
  end
end

requireは主にライブラリを読み込むために使います。書いた場所のスコープに読み込むわけではないのでファイルの一番上(マジックコメントよりは下)に書くのが一般的です。

ついでにloadとの違いを押さえておくと良いかもです。

プログラミングスクールのフィヨルドブートキャンプの提出物のレビューでよく指摘するシリーズ。

Bad:

def kana(name)
  result = ""
  if name == "komagata"
    result = "コマガタ"
  elsif name == "machida"
    result = "マチダ"
  elsif name == "tanaka"
    result = "タナカ"
  elsif name == "yamada"
    result = "ヤマダ"
  end
  result
end

Good:

def kana(name)
  {
    "komagata" => "コマガタ",
    "machida" => "マチダ",
    "tanaka" => "タナカ",
    "yamada" => "ヤマダ"
  }[name]
end

こういう分岐数が多いやつはハッシュで分岐数を削減できる。

5.2系では読み込まれてたんだけど、6.0.1にしたらデフォルトでは読み込まれなくなってるみたい。zeitwerkになったからかしら。

手動でrequireすればOK。

require "active_record/fixtures"

class NotificationMailerPreview < ActionMailer::Preview
  def came_comment
    id = ActiveRecord::FixtureSet.identify(:report_5)

    # ....
  end
end

「開発環境のユーザーのパスワードって何?」

みたいな情報の共有って面倒ですよね。any_login入れちゃうのがいいかも。

any_loginを入れると画面の左下にアイコンが出てきて、そこをクリックするとユーザーが一覧できるプルダウンメニューが出てきます。それを選択するだけでそのユーザーでログインできるというもの。

似た仕組みを用意してる人が多いとは思いますが、無いなら導入おすすめです。

igorkasyanchuk/any_login: Easy way to login as any user in system

認証ライブラリはDevise, Authlogic, Clearance, Sorceryなど色々対応してます。

Image from Gyazo

フィヨルドブートキャンプのアプリに入れてみました。

fjordllc/bootcamp: プログラマー向けEラーニングシステム

プログラミングスクールのフィヨルドブートキャンプの提出物のレビューでよく指摘するシリーズ。

rubyではメソッド呼び出しにカッコ()が必須ではありません。カッコを使わずにメソッドを呼び出すと単に変数を参照しているように見えます。

article = Article.new
article.title # メンバ変数が呼べているように見える

rubyはこれを利用してアクセサを実現している。(c#やswiftでは専用の構文を用意している)

attr_accessor :title
def title
  @title
end

def title=(title)
  @title = title
end

↑この2つは同じ。

これは自分のプログラムにも活用できる。メソッド名が名詞であって、そういうメンバ変数のアクセサとして動いているように”見えれば”問題ないメソッドになる。

def age
  today - birthday
end
user = User.new
user.age # そういうメンバ変数にみえる

実際にはそんなメンバ変数はなく、メソッドがメンバ変数のフリをしているだけだ。クラスを使う人からみれば同じように振る舞うのであれば問題ない。 複雑な処理を整理したいとき、これを使ってなるべくそのクラスのメンバ変数に見える名詞メソッドの形にしてみよう。 たくさんのメンバ変数(に見える名詞メソッド)を扱う少数のメソッドという形になると全体が把握しやすくなる。何故ならメンバ変数よりメソッドの方が入力・出力・副作用を考えなければ行けないので複雑だから。

注意

ただ、メンバ変数のように見えるメソッドが派手な副作用を持っている時、使う人から見るとエゲツない罠となる。

プログラミングスクールのフィヨルドブートキャンプの提出物のレビューでよく指摘するシリーズ。

可読性の面

可読性(他の人が読んだ時のわかりやすさ)の面から言うと、initialize(初期化)と言える処理のみ書くべきです。

そのクラスがインスタンスになっているとき、そのインスタンスが備えているべき変数の状態などを設定しておくべきです。

newしただけなのに初期化とは言えないことをやっているとそのクラスを使った人はビックリするし、勘違いから事故が起きやすくなる。

機能の面

機能の面から言うと、大抵は受け取った引数からメンバ変数を設定する程度をするのが一般的です。

例えば、newしただけで何か文字を出力してたりすると、そのクラスをテストする時に困る。拡張するときも困る。

printer = Printer.new # ここで画面に何か出ちゃう
assert printer.ready?