1. ホーム
  2. スクリプト・コラム
  3. ルビートピックス

Rubyのクラスとモジュールの書き方のスタイルガイド

2022-02-12 11:57:44

クラス定義では一貫した構造を使用します。

class Person
   # extend and include go first
   extend SomeModule
   include AnotherModule

   # constants are next
   SOME_CONSTANT = 20

   # afterwards we have attribute macros
   attr_reader :name

   # followed by other macros (if any)
   validates :name

   # public class methods are next in line
   def self.some_method
   end

   # followed by public instance methods
   def some_method
   end

   # protected and private methods are grouped near the end
   protected

   def some_protected_method
   end

   private

   def some_private_method
   end
  end



    クラスは、インスタンスを作成することが意味のある場合にのみ使用する必要があります。

 # bad
  class SomeClass
   def self.some_method
    # body omitted
   end

   def self.some_other_method
   end
  end

  # good
  module SomeClass
   module_function

   def some_method
    # body omitted
   end

   def some_other_method
   end
  end



    モジュールのインスタンスメソッドをクラスメソッドにしたい場合は、extend self よりも module_function を使うことをお勧めします。

  # bad
  module Utilities
   extend self

   def parse_something(string)
    # do stuff here
   end

   def other_utility_method(number, string)
    # do some more stuff
   end
  end

  # good
  module Utilities
   module_function

   def parse_something(string)
    # do stuff here
   end

   def other_utility_method(number, string)
    # do some more stuff
   end
  end



    クラス階層を設計する場合、その階層は必ず
    リスコフ置換原理。

    クラス階層を設計する際には、Liskov Substitution Principleに適合していることを確認する。(注意:LSP原則とは、大まかに言うと、ある関数が親クラスのインスタンスを参照している場合、その関数を子クラスのインスタンスに置き換えなければならず、関数の基本機能はそのままである、というものです。(ただし、関数は拡張することができる)。

        リスコフ置換の原則:サブタイプは、そのベースタイプを置換できなければならない <br/>
        1. T1型のオブジェクトo1に対して、T1型で定義された全てのプログラムPの動作が、全てのオブジェクトo1をo2に置き換えても変わらないようなT2型のオブジェクトo2が存在するならば、T2型はT1型のサブタイプである。 <br/>
        2. つまり、ベースクラスを使用するソフトウェア・エンティティは、そのサブクラスにも適用しなければならず、ベースクラスのオブジェクトとサブクラスのオブジェクトの違いを全く検出することができない。ベースクラスが真に再利用されるのは、派生クラスがソフトウェア実体の機能を維持したままベースクラスを置き換える場合のみである <br/>
        3. リヒター置換原理は、バーバラ・リスコフ(Barbar Liskov)によって、継承の原理として紹介されたものである。継承再利用の基礎となるものである。 <br/>
        4. リヒター置換の原則に適合しているかどうかは、その相続が合理的かどうか(欠点を隠しているかどうか)を判断する方法である。

    クラスをできるだけ堅牢にするよう努力する [SOLID](http://en.wikipedia.org/wiki/SOLID_object-oriented_design)). (

    このクラス(インスタンス)オブジェクトに含まれるオブジェクトを表現するために使用されるto_sメソッドを必ず自クラスに用意すること。

 class Person
   attr_reader :first_name, :last_name

   def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
   end

   def to_s
    "#@first_name #@last_name"
   end
  end



    attr関数メンバを使用して、各インスタンス変数のアクセッサまたはモディファイアメソッドを定義します。

 # bad
  class Person
   def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
   end

   def first_name
    @first_name
   end

   def last_name
    @last_name
   end
  end

  # good
  class Person
   attr_reader :first_name, :last_name

   def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
   end
  end



    attrの使用は避けてください。attr_reader や attr_accessor を代替に使ってください。

  # bad - creates a single attribute accessor (deprecated in 1.9)
  attr :something, true
  attr :one, :two, :three # behaves as attr_reader

  # good
  attr_accessor :something
  attr_reader :one, :two, :three



    Struct.newの使用を検討してください。これは、いくつかの些細なアクセサを定義しています。
    のコンストラクタと比較演算を行う。

  # good
  class Person
   attr_reader :first_name, :last_name

   def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
   end
  end

  # better
  class Person < Struct.new(:first_name, :last_name)
  end



    Struct.newを使用することを検討してください。これは、些細なアクセサ、コンストラクタ、および比較演算子を定義してくれます。

  # good
  class Person
   attr_accessor :first_name, :last_name

   def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
   end
  end

  # better
  Person = Struct.new(:first_name, :last_name) do
  end



    Struct.new を拡張してはいけない - それはすでに新しいクラスであり、拡張すると冗長なクラス階層を作ることになる。
    また、ファイルが何度も読み込まれると、変なエラーが発生することがあります。

    特定のクラスインスタンスを柔軟に作成するためのファクトリーメソッドの追加を検討する。

class Person
   def self.create(potions_hash)
    # body omitted
   end
  end


    継承よりもアヒル型(Duck-typing)が好まれる。

 # bad
  class Animal
   # abstract method
   def speak
   end
  end

  # extend superclass
  class Duck < Animal
   def speak
    puts 'Quack!
   end
  end

  # extend superclass
  class Dog < Animal
   def speak
    puts 'Bau!
   end
  end

  # good
  class Duck
   def speak
    puts 'Quack!
   end
  end

  class Dog
   def speak
    puts 'Bau!
   end
  end



    クラス変数(@@)の使用は、その厄介な動作のために避けてください。
    を継承しています。

    クラス変数(@)は厄介な継承の癖があるので使わないようにしましょう(子クラスでも親のクラス変数を変更することができます)。

 class Parent
   @@class_var = 'parent'

   def self.print_class_var
    puts @@class_var
   end
  end

  class Child < Parent
   @@class_var = 'child'
  end

  Parent.print_class_var # => will print "child"



    上の例でわかるように、すべてのサブクラスはクラス変数を共有し、それを直接変更することができますので、クラスのインスタンス変数を使用する方がよいアイデアです。

    メソッドにはその用途に応じて適切な可視性(private, protected)を割り当て、すべてのメソッドをpublicにしない(これがデフォルト)。これはRubyであり、Pythonではありません。

    public、protected、private などの Visibility キーワードは、その(指定された)メソッドと同じインデントを使用する必要があります。そして、異なる可視性キーワードの間にはスペースを空けてください。

 class SomeClass
   def public_method
    # ...
   end

   private

   def private_method
    # ...
   end

   def another_private_method
    # ...
   end
  end



    シングルトンメソッドを定義するにはdef self.methodを使用します。コードをリファクタリングするときに、クラス名が繰り返されないので、簡単になります。

  class TestClass
   # bad
   def TestClass.some_method
    # body omitted
   end

   # good
   def self.some_other_method
    # body omitted
   end

   # Also possible and convenient when you
   # have to define many singleton methods.
   class << self
    def first_method
     # body omitted
    end

    def second_method_etc
     # body omitted
    end
   end
  end

  class SingletonTest
   def size
    25
   end
  end

  test1 = SingletonTest.new
  test2 = SingletonTest.new
  def test2.size
   10
  end
  test1.size # => 25
  test2.size # => 10



    この例では、test1 と test2 は同じクラスですが、test2 は size メソッドを再定義しているため、異なる動作をすることになります。単一のオブジェクトしか与えないメソッドをシングルトンメソッドと呼びます。