18. Клас-променливи. RDoc. Извадки от Ruby Core и Stdlib

18. Клас-променливи. RDoc. Извадки от Ruby Core и Stdlib

18. Клас-променливи. RDoc. Извадки от Ruby Core и Stdlib

15 декември 2014

Днес

Проекти

Какво очакваме да ни изпратите?

или, как да напишете спецификация

Лоши примери (1)

Лоши примери (2)

Лоши примери

(Не)подходящи теми

или, къде Ruby е силен и къде - не

(Не)подходящи теми

изключения

The Ten Commandments of Egoless Programming

1. Understand and accept that you will make mistakes.

The point is to find them early, before they make it into production. Fortunately, except for the few of us developing rocket guidance software at JPL, mistakes are rarely fatal in our industry, so we can, and should, learn, laugh, and move on.

2. You are not your code.

Remember that the entire point of a review is to find problems, and problems will be found. Don't take it personally when one is uncovered.

3. No matter how much "karate" you know, someone else will always know more.

Such an individual can teach you some new moves if you ask. Seek and accept input from others, especially when you think it's not needed.

4. Don't rewrite code without consultation.

There's a fine line between "fixing code" and "rewriting code." Know the difference, and pursue stylistic changes within the framework of a code review, not as a lone enforcer.

5. Treat people who know less than you with respect, deference, and patience.

Nontechnical people who deal with developers on a regular basis almost universally hold the opinion that we are prima donnas at best and crybabies at worst. Don't reinforce this stereotype with anger and impatience.

6. The only constant in the world is change.

Be open to it and accept it with a smile. Look at each change to your requirements, platform, or tool as a new challenge, not as some serious inconvenience to be fought.

7. The only true authority stems from knowledge, not from position.

Knowledge engenders authority, and authority engenders respect – so if you want respect in an egoless environment, cultivate knowledge.

8. Fight for what you believe, but gracefully accept defeat.

Understand that sometimes your ideas will be overruled. Even if you do turn out to be right, don't take revenge or say, "I told you so" more than a few times at most, and don't make your dearly departed idea a martyr or rallying cry.

9. Don't be "the guy in the room."

Don't be the guy coding in the dark office emerging only to buy cola. The guy in the room is out of touch, out of sight, and out of control and has no place in an open, collaborative environment.

10. Critique code instead of people – be kind to the coder, not to the code.

As much as possible, make all of your comments positive and oriented to improving the code. Relate comments to local standards, program specs, increased performance, etc.

Малко код за загрявка

Вариант 1

без дублиране на "знание"

class CSS
  PREFIXES = ['webkit', 'moz', 'ms', 'o']

  def browser_prefixes_for(css_property_name)
    PREFIXES.map do |prefix|
      "-#{prefix}-#{css_property_name}"
    end
  end
end

Вариант 2

добавихме стандартния префикс

class CSS
  PREFIXES = ['webkit', 'moz', 'ms', 'o', nil]

  def browser_prefixes_for(css_property_name)
    PREFIXES.map do |prefix|
      prefix ? "-#{prefix}-#{css_property_name}" : css_property_name
    end
  end
end

Вариант 3

като вариант 2, но по-просто

class CSS
  PREFIXES = ['-webkit-', '-moz-', '-ms-', '-o-', '']

  def browser_prefixes_for(css_property_name)
    PREFIXES.map do |prefix|
      prefix + css_property_name
    end
  end
end

Proc Placeholder

challenges[6].solutions.size < 7 # true
challenges[6].solutions.one?(&:correct?) # true

Да се дефинира P, такова че...

(1..5).map(&P ** 2) # [1, 4, 9, 16, 25]
[[1, 2, 3, 8], [4, 5], [6]].map(&P.select(&:even?)) # [[2, 8], [4], [6]]
(1..5).map(&P)    # [1, 2, 3, 4, 5]

Ще реализираме P като клас и ще дефинираме само класови методи

class P
  class << self
    # ...
  end
end

P трябва да приема вскякви извиквания на методи...

class P
  class << self
    def method_missing(method, *args, &block)
      # ...
    end
  end
end

...и да ги пренасовча към конкретен обект

class P
  class << self
    def method_missing(method, *args, &block)
      Proc.new { |object| object.send method, *args, &block }
    end
  end
end

P трябва да се държи като идентитет, ако не е извикан метод

class P
  class << self
    def method_missing(method, *args, &block)
      Proc.new { |object| object.send method, *args, &block }
    end

    def to_proc
      Proc.new { |object| object }
    end
  end
end

P трябва да се държи като proxy и да прехвърля всеки извикан метод на обектите от колекцията...

class P < BasicObject
  class << self
    def method_missing(method, *args, &block)
      ::Proc.new { |object| object.send method, *args, &block }
    end

    def to_proc
      ::Proc.new { |object| object }
    end
  end
end

...абсолютно всеки метод

class P < BasicObject
  class << self
    instance_methods.each do |instance_method|
      undef_method instance_method
    end

    def method_missing(method, *args, &block)
      ::Proc.new { |object| object.send method, *args, &block }
    end

    def to_proc
      ::Proc.new { |object| object }
    end
  end
end

Object#eql?

1 == 1.0     # true
1.eql?(1.0)  # false

$! и $@

retry в ensure

retry изпълнява begin блока отначало.

retries_left = 3

begin
  connect_to_facebook
rescue ConnectionError
  retries_left -= 1
  retry if retries_left > 0
end

next, break, redo, retry

Има много хубава семантика за тях.

излизане от...рестартиране на...
...блокаnextredo
...методаbreakretry

retry не работи извън rescue от Ruby 1.9 насам.

Клас променливи

class Person
  @@count = 0

  def initialize
    @@count += 1
  end

  def self.how_many
    @@count
  end
end

Person.new
Person.new
Person.how_many # 2

Клас променливи

семантиката

Клас променливи

class B
  @@foo = 1
  def self.foo() @@foo end
  def self.hmm() @@bar end
end

class D < B
  @@bar = 2
  def self.bar() @@bar end
  def self.change() @@foo = 3; @@bar = 4; end
end

[B.foo, D.foo, D.bar] # [1, 1, 2]
B.hmm                 # error: NameError
D.change
[B.foo, D.foo, D.bar] # [3, 3, 4]
B.hmm                 # error: NameError
D.hmm                 # error: NameError

Class#new и Object#initialize

Всъщност, #initialize е просто instance метод.

Class#new е имплементиран горе-долу така:

class Class
  def new
    object = self.allocate
    object.send :initialize
    object
  end
end

Object#dup, Object#clone, #initialize_copy

Документация в Ruby

RDoc

YARD

Други - Rails API документацията

Други - API Dock

RDoc

Подробно относно RDoc

Ruby core

Struct

Struct

пример от документацията

Виждате, че може да добавите и свои методи там

Customer = Struct.new(:name, :address) do
  def greeting
    "Hello #{name}!"
  end
end

dave = Customer.new('Dave', '123 Main')

dave.name     # "Dave"
dave.address  # "123 Main"
dave.greeting # "Hello Dave!"

Struct

Обектите от тип Struct приличат на колекции (хешове):

Customer = Struct.new(:name, :address, :zip)
john = Customer.new('John Doe', '123 Maple, Anytown NC', 12345)

john.name      # "John Doe"
john['name']   # "John Doe"
john[:name]    # "John Doe"
john[0]        # "John Doe"

john.length    # 3
john.each      # #<Enumerator: #<struct Customer name="John Doe", address="123 Maple, Anytown NC", zip=12345>:each>

Приложение на Struct

Удобно е да се ползва в тестове. За production код – по-рядко.

Comparable

Comparable

пример

class Money
  include Comparable

  attr :amount, :currency

  def initialize(amount, currency)
    @amount, @currency = amount, currency
  end

  def <=>(money)
    return unless currency == money.currency
    amount <=> money.amount
  end
end

Money.new(15, :BGN) < Money.new(30, :BGN) # true

Marshal

Marshal

пример

data = [42, :answer, {foo: 'bar'}]

serizlized = Marshal.dump data

serizlized                       # "\x04\b[\bi/:\vanswer{\x06:\bfooI\"\bbar\x06:\x06ET"
Marshal.load(serizlized) == data # true
Marshal.load(serizlized)         # [42, :answer, {:foo=>"bar"}]

Marshal

употреба и бележки

IO и File

File - два примера

Прочитане на цял файл като низ:

File.read('/etc/resolv.conf') # => "The contents of the file as a string."

Отваряне на файл, работа с обекта и автоматично затваряне на файла при излизане от блока:

File.open('foo/bar.txt', 'w') do |file|
  file.write 'Some data.'
  file.puts  'Some other data, with a newline at the end.'
  file.puts  'File will be closed automatically, even in case of an exception in the block.'
end

RubyVM::InstructionSequence

RubyVM::InstructionSequence

пример

code = 'puts ["a" * 10].first'
compiled = RubyVM::InstructionSequence.compile(code)
puts compiled.disasm

Резултат:

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (   1)
0002 putself
0003 putstring        "a"
0005 putobject        10
0007 opt_mult         <callinfo!mid:*, argc:1, ARGS_SKIP>
0009 newarray         1
0011 opt_send_simple  <callinfo!mid:first, argc:0, ARGS_SKIP>
0013 opt_send_simple  <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0015 leave

RubyVM::InstructionSequence

още един пример

puts RubyVM::InstructionSequence.compile('a, b = 1, 2').disasm

Можем да видим, че a, b = 1, 2 минава през създаване на един обект тип Array:

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 3, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@4] s1)
[ 3] a          [ 2] b
0000 trace            1                                               (   1)
0002 duparray         [1, 2]
0004 dup
0005 expandarray      2, 0
0008 setlocal_OP__WC__0 3
0010 setlocal_OP__WC__0 2
0012 leave

Стандартната библиотека на Ruby

Налични библиотеки

abbrev       base64          benchmark    bigdecimal          cgi            cmath
coverage     csv             curses       date                dbm            debug
delegate     digest          dl           drb                 e2mmap         English
erb          etc             extmk        fcntl               fiddle         fileutils
find         forwardable     gdbm         getoptlong          gserver        io/console
io/nonblock  io/wait         ipaddr       irb                 json           logger
mathn        matrix          minitest     minitest/benchmark  minitest/spec  mkmf
monitor      mutex_m         net/ftp      net/http            net/imap       net/pop
net/smtp     net/telnet      nkf          objspace            observer       open-uri
open3        openssl         optparse     ostruct             pathname       pp
prettyprint  prime           profile      profiler            pstore         psych
pty          racc            racc/parser  rake                rdoc           readline
resolv       resolv-replace  rexml        rinda               ripper         rss
rubygems     scanf           sdbm         securerandom        set            shell
shellwords   singleton       socket       stringio            strscan        sync
syslog       tempfile        test/unit    thread              thwait         time
timeout      tk              tmpdir       tracer              tsort          un
uri          weakref         webrick      win32ole            xmlrpc         yaml
zlib

Какво сме ползвали досега от Stdlib?

OpenStruct

OpenStruct - пример

require 'ostruct'

john = OpenStruct.new
john.name = 'John Doe'
john.age  = 33

john.name     # "John Doe"
john.age      # 33
john.address  # nil

Въпроси