Francis's Octopress Blog

A blogging framework for hackers.

No One Is Coming

No one is coming

Happiness: resilience, optimism, self-faith, sence-meaning, social behavior, helping others Stop blaming others. Take responsibility of your life. No one is coming. You are responsible for your life, for your self-confidence, for your self-esteem, for your happiness. No one is coming. It’s up to you to make the most out of this experience. People are comfortably numb. Clutivating personal growth, working on the positive. Idealism and good intentions are not enough to resolve conflict.

ruby-style-guide.en.md

ruby-style-guide.en.md

Prelude

Style is what separates the good from the great.  – Bozhidar Batsov

One thing has always bothered me as Ruby developer – Python developers have a great programming style reference (PEP-8) and we never got an official guide, documenting Ruby coding style and best practices. And I do believe that style matters. I also believe that such fine fellows, like us Ruby developers, should be quite capable to produce this coveted document.

This guide started its life as our internal company Ruby coding guidelines (written by yours truly). At some point I decided that the work I was doing might be interesting to members of the Ruby community in general and that the world had little need for another internal company guideline. But the world could certainly benefit from a community-driven and community-sanctioned set of practices, idioms and style prescriptions for Ruby programming.

Since the inception of the guide I’ve received a lot of feedback from members of the exceptional Ruby community around the world. Thanks for all the suggestions and the support! Together we can make a resource beneficial to each and every Ruby developer out there.

By the way, if you’re into Rails you might want to check out the complementary Ruby on Rails 3 Style Guide.

The Ruby Style Guide

This Ruby style guide recommends best practices so that real-world Ruby programmers can write code that can be maintained by other real-world Ruby programmers. A style guide that reflects real-world usage gets used, and a style guide that holds to an ideal that has been rejected by the people it is supposed to help risks not getting used at all – no matter how good it is.

The guide is separated into several sections of related rules. I’ve tried to add the rationale behind the rules (if it’s omitted I’ve assumed that is pretty obvious).

I didn’t come up with all the rules out of nowhere – they are mostly based on my extensive career as a professional software engineer, feedback and suggestions from members of the Ruby community and various highly regarded Ruby programming resources, such as“Programming Ruby 1.9” and “The Ruby Programming Language”.

The guide is still a work in progress – some rules are lacking examples, some rules don’t have examples that illustrate them clearly enough. In due time these issues will be addressed – just keep them in mind for now.

You can generate a PDF or an HTML copy of this guide using Transmuter.

Source Code Layout

Nearly everybody is convinced that every style but their own is ugly and unreadable. Leave out the “but their own” and they’re probably right…  – Jerry Coffin (on indentation)
  • Use UTF-8 as the source file encoding.
  • Use two spaces per indentation level.
    # good
    def some_method
      do_something
    end
    
    # bad - four spaces
    def some_method
        do_something
    end
  • Use Unix-style line endings. (*BSD/Solaris/Linux/OSX users are covered by default, Windows users have to be extra careful.)
    • If you’re using Git you might want to add the following configuration setting to protect your project from Windows line endings creeping in: $ git config --global core.autocrlf true
  • Use spaces around operators, after commas, colons and semicolons, around { and before }. Whitespace might be (mostly) irrelevant to the Ruby interpreter, but its proper use is the key to writing easily readable code.
    sum = 1 + 2
    a, b = 1, 2
    1 > 2 ? true : false; puts 'Hi'
    [1, 2, 3].each { |e| puts e }
    The only exception is when using the exponent operator:
    # bad
    e = M * c ** 2
    
    # good
    e = M * c**2
  • No spaces after ([ or before ]).
    some(arg).other
    [1, 2, 3].length
  • Indent when as deep as case. I know that many would disagree with this one, but it’s the style established in both the “The Ruby Programming Language” and “Programming Ruby”.
    case
    when song.name == 'Misty'
      puts 'Not again!'
    when song.duration > 120
      puts 'Too long!'
    when Time.now.hour > 21
      puts "It's too late"
    else
      song.play
    end
    
    kind = case year
           when 1850..1889 then 'Blues'
           when 1890..1909 then 'Ragtime'
           when 1910..1929 then 'New Orleans Jazz'
           when 1930..1939 then 'Swing'
           when 1940..1950 then 'Bebop'
           else 'Jazz'
           end
  • Use empty lines between defs and to break up a method into logical paragraphs.
    def some_method
      data = initialize(options)
    
      data.manipulate!
    
      data.result
    end
    
    def some_method
      result
    end
  • Align the parameters of a method call if they span over multiple lines.
    # starting point (line is too long)
    def send_mail(source)
      Mailer.deliver(to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text)
    end
    
    # bad (normal indent)
    def send_mail(source)
      Mailer.deliver(
        to: 'bob@example.com',
        from: 'us@example.com',
        subject: 'Important message',
        body: source.text)
    end
    
    # bad (double indent)
    def send_mail(source)
      Mailer.deliver(
          to: 'bob@example.com',
          from: 'us@example.com',
          subject: 'Important message',
          body: source.text)
    end
    
    # good
    def send_mail(source)
      Mailer.deliver(to: 'bob@example.com',
                     from: 'us@example.com',
                     subject: 'Important message',
                     body: source.text)
    end
  • Use RDoc and its conventions for API documentation. Don’t put an empty line between the comment block and the def.
  • Keep lines fewer than 80 characters.
  • Avoid trailing whitespace.

Syntax

  • Use def with parentheses when there are arguments. Omit the parentheses when the method doesn’t accept any arguments.
     def some_method
       # body omitted
     end
    
     def some_method_with_arguments(arg1, arg2)
       # body omitted
     end
  • Never use for, unless you know exactly why. Most of the time iterators should be used instead. for is implemented in terms ofeach (so you’re adding a level of indirection), but with a twist - for doesn’t introduce a new scope (unlike each) and variables defined in its block will be visible outside it.
    arr = [1, 2, 3]
    
    # bad
    for elem in arr do
      puts elem
    end
    
    # good
    arr.each { |elem| puts elem }
  • Never use then for multi-line if/unless.
    # bad
    if some_condition then
      # body omitted
    end
    
    # good
    if some_condition
      # body omitted
    end
  • Favor the ternary operator(?:) over if/then/else/end constructs. It’s more common and obviously more concise.
    # bad
    result = if some_condition then something else something_else end
    
    # good
    result = some_condition ? something : something_else
  • Use one expression per branch in a ternary operator. This also means that ternary operators must not be nested. Prefer if/elseconstructs in these cases.
    # bad
    some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
    
    # good
    if some_condition
      nested_condition ? nested_something : nested_something_else
    else
      something_else
    end
  • Never use if x: ... - it is removed in Ruby 1.9. Use the ternary operator instead.
    # bad
    result = if some_condition: something else something_else end
    
    # good
    result = some_condition ? something : something_else
  • Never use if x; .... Use the ternary operator instead.
  • Use when x then ... for one-line cases. The alternative syntax when x: ... is removed in Ruby 1.9.
  • Never use when x; .... See the previous rule.
  • Use &&/|| for boolean expressions, and/or for control flow. (Rule of thumb: If you have to use outer parentheses, you are using the wrong operators.)
    # boolean expression
    if some_condition && some_other_condition
      do_something
    end
    
    # control flow
    document.saved? or document.save!
  • Avoid multi-line ?: (the ternary operator), use if/unless instead.
  • Favor modifier if/unless usage when you have a single-line body. Another good alternative is the usage of control flow and/or.
    # bad
    if some_condition
      do_something
    end
    
    # good
    do_something if some_condition
    
    # another good option
    some_condition and do_something
  • Favor unless over if for negative conditions (or control flow or).
    # bad
    do_something if !some_condition
    
    # good
    do_something unless some_condition
    
    # another good option
    some_condition or do_something
  • Never use unless with else. Rewrite these with the positive case first.
    # bad
    unless success?
      puts 'failure'
    else
      puts 'success'
    end
    
    # good
    if success?
      puts 'success'
    else
      puts 'failure'
    end
  • Don’t use parentheses around the condition of an if/unless/while, unless the condition contains an assignment (see “Using the return value of =” below).
    # bad
    if (x > 10)
      # body omitted
    end
    
    # good
    if x > 10
      # body omitted
    end
    
    # ok
    if (x = self.next_value)
      # body omitted
    end
  • Omit parentheses around parameters for methods that are part of an internal DSL (e.g. Rake, Rails, RSpec), methods that are with “keyword” status in Ruby (e.g. attr_readerputs) and attribute access methods. Use parentheses around the arguments of all other method invocations.
    class Person
      attr_reader :name, :age
    
      # omitted
    end
    
    temperance = Person.new('Temperance', 30)
    temperance.name
    
    puts temperance.age
    
    x = Math.sin(y)
    array.delete(e)
  • Prefer {...} over do...end for single-line blocks. Avoid using {...} for multi-line blocks (multiline chaining is always ugly). Always use do...end for “control flow” and “method definitions” (e.g. in Rakefiles and certain DSLs). Avoid do...end when chaining.
    names = ["Bozhidar", "Steve", "Sarah"]
    
    # good
    names.each { |name| puts name }
    
    # bad
    names.each do |name|
      puts name
    end
    
    # good
    names.select { |name| name.start_with?("S") }.map { |name| name.upcase }
    
    # bad
    names.select do |name|
      name.start_with?("S")
    end.map { |name| name.upcase }
    Some will argue that multiline chaining would look OK with the use of {…}, but they should ask themselves - it this code really readable and can’t the blocks contents be extracted into nifty methods.
  • Avoid return where not required.
    # bad
    def some_method(some_arr)
      return some_arr.size
    end
    
    # good
    def some_method(some_arr)
      some_arr.size
    end
  • Use spaces around the = operator when assigning default values to method parameters:
    # bad
    def some_method(arg1=:default, arg2=nil, arg3=[])
      # do something...
    end
    
    # good
    def some_method(arg1 = :default, arg2 = nil, arg3 = [])
      # do something...
    end
    While several Ruby books suggest the first style, the second is much more prominent in practice (and arguably a bit more readable).
  • Avoid line continuation (\) where not required. In practice, avoid using line continuations at all.
    # bad
    result = 1 - \
             2
    
    # good (but still ugly as hell)
    result = 1 \
             - 2
  • Using the return value of = (an assignment) is ok, but surround the assignment with parenthesis.
    # good - shows intented use of assignment
    if (v = array.grep(/foo/)) ...
    
    # bad
    if v = array.grep(/foo/) ...
    
    # also good - shows intended use of assignment and has correct precedence.
    if (v = self.next_value) == "hello" ...
  • Use ||= freely to initialize variables.
    # set name to Bozhidar, only if it's nil or false
    name ||= 'Bozhidar'
  • Don’t use ||= to initialize boolean variables. (Consider what would happen if the current value happened to be false.)
    # bad - would set enabled to true even if it was false
    enabled ||= true
    
    # good
    enabled = true if enabled.nil?
  • Avoid using Perl-style special variables (like $0-9$`, etc. ). They are quite cryptic and their use in anything but one-liner scripts is discouraged.
  • Never put a space between a method name and the opening parenthesis.
    # bad
    f (3 + 2) + 1
    
    # good
    f(3 + 2) + 1
  • If the first argument to a method begins with an open parenthesis, always use parentheses in the method invocation. For example, write f((3 + 2) + 1).
  • Always run the Ruby interpreter with the -w option so it will warn you if you forget either of the rules above!
  • When the keys of your hash are symbols use the Ruby 1.9 hash literal syntax.
    # bad
    hash = { :one => 1, :two => 2 }
    
    # good
    hash = { one: 1, two: 2 }
  • Use the new lambda literal syntax.
    # bad
    lambda = lambda { |a, b| a + b }
    lambda.call(1, 2)
    
    # good
    lambda = ->(a, b) { a + b }
    lambda.(1, 2)
  • Use _ for unused block parameters.
    # bad
    result = hash.map { |k, v| v + 1 }
    
    # good
    result = hash.map { |_, v| v + 1 }

Naming

The only real difficulties in programming are cache invalidation and naming things.  – Phil Karlton
  • Use snake_case for methods and variables.
  • Use CamelCase for classes and modules. (Keep acronyms like HTTP, RFC, XML uppercase.)
  • Use SCREAMING_SNAKE_CASE for other constants.
  • The names of predicate methods (methods that return a boolean value) should end in a question mark. (i.e. Array#empty?).
  • The names of potentially “dangerous” methods (i.e. methods that modify self or the arguments, exit!, etc.) should end with an exclamation mark.
  • When using reduce with short blocks, name the arguments |a, e| (accumulator, element).
  • When defining binary operators, name the argument other.
    def +(other)
      # body omitted
    end
  • Prefer map over collectfind over detectselect over find_allreduce over inject and size over length. This is not a hard requirement; if the use of the alias enhances readability, it’s ok to use it. The rhyming methods are inherited from Smalltalk and are not common in other programming languages. The reason the use of select is encouraged over find_all is that it goes together nicely with reject and its name is pretty self-explanatory.

Comments

Good code is its own best documentation. As you’re about to add a comment, ask yourself, “How can I improve the code so that this comment isn’t needed?” Improve the code and then document it to make it even clearer.  – Steve McConnell
  • Write self-documenting code and ignore the rest of this section. Seriously!
  • Comments longer than a word are capitalized and use punctuation. Use one space after periods.
  • Avoid superfluous comments.
    # bad
    counter += 1 # increments counter by one
  • Keep existing comments up-to-date. No comment is better than an outdated comment.
  • Avoid writing comments to explain bad code. Refactor the code to make it self-explanatory. (Do or do not - there is no try.)

Annotations

  • Annotations should usually be written on the line immediately above the relevant code.
  • The annotation keyword is followed by a colon and a space, then a note describing the problem.
  • If multiple lines are required to describe the problem, subsequent lines should be indented two spaces after the #.
    def bar
      # FIXME: This has crashed occasionally since v3.2.1. It may
      #   be related to the BarBazUtil upgrade.
      baz(:quux)
    end
  • In cases where the problem is so obvious that any documentation would be redundant, annotations may be left at the end of the offending line with no note. This usage should be the exception and not the rule.
    def bar
      sleep 100 # OPTIMIZE
    end
  • Use TODO to note missing features or functionality that should be added at a later date.
  • Use FIXME to note broken code that needs to be fixed.
  • Use OPTIMIZE to note slow or inefficient code that may cause performance problems.
  • Use HACK to note code smells where questionable coding practices were used and should be refactored away.
  • Use REVIEW to note anything that should be looked at to confirm it is working as intended. For example:REVIEW: Are we sure this is how the client does X currently?
  • Use other custom annotation keywords if it feels appropriate, but be sure to document them in your project’s README or similar.

Classes

  • When designing class hierarchies make sure that they conform to the Liskov Substitution Principle.
  • Try to make your classes as SOLID as possible.
  • Always supply a proper to_s method for classes that represent domain objects.
    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
  • Use the attr family of functions to define trivial accessors or mutators.
    # 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
  • Consider adding factory methods to provide additional sensible ways to create instances of a particular class.
    class Person
      def self.create(options_hash)
        # body omitted
      end
    end
  • Prefer duck-typing over inheritance.
    # bad
    class Animal
      # abstract method
      def speak
      end
    end
    
    # extend superclass
    class Duck < Animal
      def speak
        puts 'Quack! Quack'
      end
    end
    
    # extend superclass
    class Dog < Animal
      def speak
        puts 'Bau! Bau!'
      end
    end
    
    # good
    class Duck
      def speak
        puts 'Quack! Quack'
      end
    end
    
    class Dog
      def speak
        puts 'Bau! Bau!'
      end
    end
  • Avoid the usage of class (@@) variables due to their “nasty” behavior in inheritance.
    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"
    As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.
  • Assign proper visibility levels to methods (privateprotected) in accordance with their intended usage. Don’t go off leaving everything public (which is the default). After all we’re coding in Ruby now, not in Python.
  • Indent the publicprotected, and private methods as much the method definitions they apply to. Leave one blank line above them.
    class SomeClass
      def public_method
        # ...
      end
    
      private
      def private_method
        # ...
      end
    end
  • Use def self.method to define singleton methods. This makes the methods more resistant to refactoring changes.
    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

Exceptions

  • Don’t suppress exceptions.
    begin
      # an exception occurs here
    rescue SomeError
      # the rescue clause does absolutely nothing
    end
  • Don’t use exceptions for flow of control.
    # bad
    begin
      n / d
    rescue ZeroDivisionError
      puts "Cannot divide by 0!"
    end
    
    # good
    if n.zero?
      puts "Cannot divide by 0!"
    else
      n / d
  • Avoid rescuing the Exception class.
    # bad 
    begin
      # an exception occurs here
    rescue
      # exception handling
    end
    
    # still bad
    begin
      # an exception occurs here
    rescue Exception
      # exception handling
    end
  • Put more specific exceptions higher up the rescue chain, otherwise they’ll never be rescued from.
    # bad
    begin
      # some code
    rescue Exception => e
      # some handling
    rescue StandardError => e
      # some handling
    end
    
    # good
    begin
      # some code
    rescue StandardError => e
      # some handling
    rescue Exception => e
      # some handling
    end
  • Release external resources obtained by your program in an ensure block.
    f = File.open("testfile")
    begin
      # .. process
    rescue
      # .. handle error
    ensure
      f.close unless f.nil?
    end
  • Favor the use of exceptions for the standard library over introducing new exception classes.

Collections

  • Prefer %w to the literal array syntax when you need an array of strings.
    # bad
    STATES = ['draft', 'open', 'closed']
    
    # good
    STATES = %w(draft open closed)
  • Avoid the creation of huge gaps in arrays.
    arr = []
    arr[100] = 1 # now you have an array with lots of nils
  • Use Set instead of Array when dealing with unique elements. Set implements a collection of unordered values with no duplicates. This is a hybrid of Array’s intuitive inter-operation facilities and Hash’s fast lookup.
  • Use symbols instead of strings as hash keys.
    # bad
    hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
    
    # good
    hash = { one: 1, two: 2, three: 3 }
  • Avoid the use of mutable object as hash keys.
  • Use the new 1.9 literal hash syntax in preference to the hashrocket syntax.
    # bad
    hash = { :one => 1, :two => 2, :three => 3 }
    
    # good
    hash = { one: 1, two: 2, three: 3 }
  • Rely on the fact that hashes in 1.9 are ordered.
  • Never modify a collection while traversing it.

Strings

  • Prefer string interpolation instead of string concatenation:
    # bad
    email_with_name = user.name + ' <' + user.email + '>'
    
    # good
    email_with_name = "#{user.name} <#{user.email}>"
  • Prefer single-quoted strings when you don’t need string interpolation or special symbols such as \t\n', etc.
    # bad
    name = "Bozhidar"
    
    # good
    name = 'Bozhidar'
  • Don’t use {} around instance variables being interpolated into a string.
    class Person
      attr_reader :first_name, :last_name
    
      def initialize(first_name, last_name)
        @first_name = first_name
        @last_name = last_name
      end
    
      # bad
      def to_s
        "#{@first_name} #{@last_name}"
      end
    
      # good
      def to_s
        "#@first_name #@last_name"
      end
    end
  • Avoid using String#+ when you need to construct large data chunks. Instead, use String#<<. Concatenation mutates the string instance in-place and is always faster than String#+, which creates a bunch of new string objects.
    # good and also fast
    html = ''
    html << '<h1>Page title</h1>'
    
    paragraphs.each do |paragraph|
      html << "<p>#{paragraph}</p>"
    end

Regular Expressions

  • Don’t use regular expressions if you just need plain text search in string: string['text']
  • For simple constructions you can use regexp directly through string index.
    match = string[/regexp/]             # get content of matched regexp
    first_group = string[/text(grp)/, 1] # get content of captured group
    string[/text (grp)/, 1] = 'replace'  # string => 'text replace'
  • Use non capturing groups when you don’t use captured result of parenthesis.
    /(first|second)/   # bad
    /(?:first|second)/ # good
  • Avoid using $1-9 as it can be hard to track what they contain. Named groups can be used instead.
    # bad
    /(regexp)/ =~ string
    ...
    process $1
    
    # good
    /(?<meaningful_var>regexp)/ =~ string
    ...
    process meaningful_var
  • Character classes have only few special characters you should care about: ^-\], so don’t escape . or brackets in[].
  • Be careful with ^ and $ as they match start/end of line, not string endings. If you want to match the whole string use: \A and\Z.
    string = "some injection\nusername"
    string[/^username$/]   # matches
    string[/\Ausername\Z/] # don't match
  • Use x modifier for complex regexps. This makes them more readable and you can add some useful comments. Just be careful as spaces are ignored.
    regexp = %r{
      start         # some text
      \s            # white space char
      (group)       # first group
      (?:alt1|alt2) # some alternation
      end
    }x
  • For complex replacements sub/gsub can be used with block or hash.

Percent Literals

  • Use %w freely.
    STATES = %w(draft open closed)
  • Use %() for single-line strings which require both interpolation and embedded double-quotes. For multi-line strings, prefer heredocs.
    # bad (no interpolation needed)
    %(<div>Some text</div>)
    # should be '<div>Some text</div>'
    
    # bad (no double-quotes)
    %(This is #{quality} style)
    # should be "This is #{quality} style"
    
    # bad (multiple lines)
    %(<div>\n<span>#{exclamation}</span>\n</div>)
    # should be a heredoc.
    
    # good (requires interpolation, has quotes, single line)
    %(<tr><td>#{name}</td>)
  • Use %r only for regular expressions matching more than one ‘/’ character.
    # bad
    %r(\s+)
    
    # still bad
    %r(^/(.*)$)
    # should be /^\/(.*)$/
    
    # good
    %r(^/blog/2011/(.*)$)
  • Avoid %q%Q%x%s, and %W.
  • Prefer () as delimiters for all % literals.

Metaprogramming

  • Do not mess around in core classes when writing libraries. (Do not monkey patch them.)

Misc

  • Write ruby -w safe code.
  • Avoid hashes as optional parameters. Does the method do too much?
  • Avoid methods longer than 10 LOC (lines of code). Ideally, most methods will be shorter than 5 LOC. Empty lines do not contribute to the relevant LOC.
  • Avoid parameter lists longer than three or four parameters.
  • If you really have to, add “global” methods to Kernel and make them private.
  • Use class instance variables instead of global variables.
    #bad
    $foo_bar = 1
    
    #good
    class Foo
      class << self
        attr_accessor :bar
      end
    end
    
    Foo.bar = 1
  • Avoid alias when alias_method will do.
  • Use OptionParser for parsing complex command line options and ruby -s for trivial command line options.
  • Code in a functional way, avoiding mutation when that makes sense.
  • Avoid needless metaprogramming.
  • Do not mutate arguments unless that is the purpose of the method.
  • Avoid more than three levels of block nesting.
  • Be consistent. In an ideal world, be consistent with these guidelines.
  • Use common sense.

Contributing

Nothing written in this guide is set in stone. It’s my desire to work together with everyone interested in Ruby coding style, so that we could ultimately create a resource that will be beneficial to the entire Ruby community.

Feel free to open tickets or send pull requests with improvements. Thanks in advance for your help!

Spread the Word

A community-driven style guide is of little use to a community that doesn’t know about its existence. Tweet about the guide, share it with your friends and colleagues. Every comment, suggestion or opinion we get makes the guide just a little bit better. And we want to have the best possible guide, don’t we?

Ruby-style-guide-zh-cn

ruby-style-guide-zh-cn

风格可以用来区分从好到卓越。 – Bozhidar Batsov

有一件事情总是困扰着,作为Ruby程序员的我 – python 开发者都有一个很棒的编程风格参考 (PEP-8),然而我们从没有一个官方的(公认)的guide,Ruby代码风格文档和最佳实践。而且我信赖这些风格(的一些约定)。我也相信下面的一些好东西,像我们这样的Ruby开发者,也应该有能力写出这样的梦寐以求的文档。

这份指南诞生于我们公司内部的Ruby编程准则,基于Ruby社区的大多数成员会对我正在做的有兴趣这样的发点,我决定做这样的工作,而且世界上很少需要另一个公司内部的(编程)准则。但是这个世界将会一定有益于社区驱动的以及社区认可的,Ruby习惯和风格实践。

自从这个guide(发表)以来,我收到了很多来自优秀的Ruby社区的世界范围内的成员的回馈。感谢所有的建议和支持!集我们大家之力,我们可以创作出对每一个Ruby开发人员有益的资源。

补充,如果你正在使用rails你可能会希望查阅Ruby on Rails 3 Style Guide.

Ruby 风格指南

这个Ruby风格指南推荐(一些)最佳实践使得现实世界中的Ruby程序员可以写出能够被其他真是世界的Ruby程序员维护的代码。一个风格指南反映了真实世界的使用习惯,同时一个风格指南紧紧把握一个观点那就是人们拒绝接受任何有可能无法使用指南的风险,无论它多好。

这个指南被分为几个具有相关的rules的几节。我尝试给rules添加合理的解释(如果它被省略我假设它相当的明显了)。

我并没有列举所有的rules – 它们大多数基于我作为一个专业的软件工程师的广泛生涯,回馈和来自Ruby社区成员的建议以及各种备受推崇的Ruby编程资源,例如“Programming Ruby 1.9” 和 “The Ruby Programming Language”.

这个指南仍然在工作进程中 – 一些rules缺乏例子,一些rules没有合适的例子来使得它们足够明了。

你可以使用Transmuter.来生成指南的PDF或者HTML的copy。

源代码布局

附近的每个人都深信每一个风格除了他们自己的都是 丑陋的并且难以阅读的。脱离”but their own”那么他们 完全正确…  – Jerry Coffin (on indentation缩进)
  • 使用 UTF-8 作为源文件编码。
  • 每个缩进级别使用两个 spaces
    # good
    def some_method
      do_something
    end
    
    # bad - four spaces
    def some_method
        do_something
    end
  • 使用Unix-风格行结束。(*BSD/Solaris/Linux/OSX 用户被涵盖为默认,Windows 用户必须特别小心.) > \n是换行,英文是LineFeed,ASCII码是0xA。 > \r是回车,英文是Carriage Return ,ASCII码是0xD。 > windows下enter是 \n\r,unix下是\n,mac下是\r
    • 如果你正在使用Git你可能会想要添加下面的配置设置来保护你的项目(避免)Windows蔓延过来的行结束符:$ git config --global core.autecrlf true
  • 使用空格:在操作符旁;逗号,冒号和分号后;在 {旁和在 }之前,大多数空格可能对Ruby解释(代码)无关,但是它的恰当使用是让代码变得易读的关键。
    sum = 1 + 2
    a, b = 1, 2
    1 > 2 ? true : false; puts 'Hi'
    [1, 2, 3].each { |e| puts e }
    唯一的例外是当使用指数操作时:
    # bad
    e = M * c ** 2
    
    # good
    e = M * c**2
  • 没有空格 ([之后或者 ])之前。
    some(arg).other
    [1, 2, 3].length
  • whencase 缩进深度一致。我知道很多人会不同意这点,但是它是”The Ruby Programming Language” 和 “Programming Ruby”中公认的风格。
    case
    when song.name == 'Misty'
      puts 'Not again!'
    when song.duraton > 120
      puts 'Too long!'
    when Time.now > 21
      puts "It's too late"
    else
      song.play
    end
    
    kind = case year
           when 1850..1889 then 'Blues'
           when 1890..1909 then 'Ragtime'
           when 1910..1929 then 'New Orleans Jazz'
           when 1930..1939 then 'Swing'
           when 1940..1950 then 'Bebop'
           else 'Jazz'
           end
  • 使用空行在 defs 并且一个方法根据逻辑段来隔开。
    def some_method
      data = initialize(options)
    
      data.manipulate!
    
      data.result
    end
    
    def some_methods
      result
    end
  • 如果一个方法的调用参数分割为多行将它们于方法名对齐。
    # starting point (line is too long)
    def send_mail(source)
      Mailer.deliver(to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text)
    end
    # bad (normal indent)
    def send_mail(source)
      Mailer.deliver(
        to: 'bob@example.com',
        from: 'us@example.com',
        subject: 'Important message',
        body: source.text)
    end
    
    # bad (double indent)
    def send_mail(source)
      Mailer.deliver(
          to: 'bob@example.com',
          from: 'us@example.com',
          subject: 'Important message',
          body: source.text)
    end
    # good
    def send_mail(source)
      Mailer.deliver(to: 'bob@example.com',
                     from: 'us@example.com',
                     subject: 'Important message',
                     body: source.text)
    end
  • 在 API 文档中使用 RDoc和它的公约。不要在注释代码块和def之间加入空行。
  • 保持每一行少于80字符。
  • 避免尾随空格。

语法

  • 使用括号将def的参数括起来。当方法不接收任何参数的时候忽略括号。
    def some_method
      # body omitted
    end
    
    def some_method_with_arguments(arg1, arg2)
      # body omitted
    end
  • 从来不要使用 for, 除非你知道使用它的准确原因。大多数时候迭代器都可以用来替代for。for is implemented in terms ofeach#foreach的组实现 (因此你正间接添加了一级),但是有一个小道道 - for并不包含一个新的scope(不像each)并且在它的块中定义的变量在外面也是可以访问的。
    arr = [1, 2, 3]
    
    # bad
    for elem in arr do 
      puts elem
    end
    
    puts elem # => 3
    
    # good
    arr.each { |elem| puts elem }
  • 在多行的if/unless中坚决不要使用then.
    # bad
    if some_condition then
      # body omitten
    end
    
    # good
    if some_condition
      # body omitted
    end
  • 喜欢三元操作运算(?:)超过if/then/else/end结果。 它更加普遍而且明显的更加简洁。
    # bad
    result = if some_condition then something else something_else end
    
    # good
    result = some_condition ? something : something_else
  • 使用一个表达式在三元操作运算的每一个分支下面只使用一个表达式。也就是说三元操作符不要被嵌套。在这样的情形中宁可使用if/else
    # bad
    some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
    
    # good
    if some_condition
      nested_condition ? nested_something : nested_something_else
    else
      something_else
    end
  • 使用三元操作运算代替if x: ...
  • 在 one-line cases 的时候使用when x then ...。替代的语法when x: xxx已经在Ruby 1.9中移除。
  • 不要使用when x; ...。查看上面的规则。
  • 布尔表达式使用&&/||and/of用于控制流程。(经验Rule:如果你必须使用额外的括号(表达逻辑),那么你正在使用错误的的操作符。)
    # boolean expression
    if some_condition && some_other_condition
      do_something
    end
    
    # control flow
    document.save? or document.save!
  • 避免多行?:(三元操作运算),使用if/unless替代。
  • 在单行语句的时候喜爱使用if/unless修饰符。另一个好的选择就是使and/of来做流程控制。
    # bad
    if some_condition
      do_something
    end
    
    # good
    do_something if some_condition
    
    # another good option
    some_condition and do_something
  • 在否定条件下喜欢unless超过if(或者控制流程 or)。
    # bad
    do_something if !some_condition
    
    # good
    do_something unless some_condition
    
    # another good option
    some_condition or do_something
  • 不要使用else搭配unless。将其的语义重写为肯定形式。
    # bad
    unless sucess?
      puts 'failure'
    else
      puts 'sucess'
    end
    
    # good
    if sucess?
      puts 'sucess'
    else
      puts 'failure'
    end
  • 不要在if/unless/while将条件旁括起来,除非这个条件包含一个参数(参见下面 “使用=返回值”)。
    # bad
    if (x>10)
      # body omitted
    end
    
    # good
    if x > 10
      # body omitted
    end
    
    # ok
    if (x = self.next_value)
      # body omitted
    end
  • DSL(e.g. Rake, Rails, RSpec)里的方法,Ruby“关键字”方法(e.g. attr_readerputs)以及属性访问方法,所带参数忽略括号。使用括号将在其他方法调用的参数括起来。
    class Person
      attr_reader :name, :age
    
      # omitted
    end
    
    temperance = Person.new('Temperance', 30)
    temperance.name
    
    puts temperance.age
    
    x = Math.sin(y)
    array.delete(e)
  • 在单行代码块的时候宁愿使用{...}而不是do...end。避免在多行代码块使用{...}(多行链式通常变得非常丑陋)。通常使用do...end来做流程控制方法定义(例如 在Rakefiles和某些DSLs中)。避免在链式调用中使用do...end
    names = ["Bozhidar", "Steve", "Sarah"]
    
    #good
    names.each { |name| puts name }
    
    #bad
    names.each do |name|
      puts name
    end
    
    # good
    names.select { |name| name.start_with?("S") }.map { |name| name.upcase }
    
    # bad
    names.select do |name|
      name.start_with?("S")
    end.map { |name| name.upcase }
    有人会争论多行链式看起来和使用{...}一样工作,但是他们问问自己 - 这样的代码真的有可读性码并且为什么代码块中的内容不能被提取到美丽的methods。
  • 避免在不需要的地方使用return
    # bad
    def some_method(some_arr)
      return some_arr.size
    end
    
    # good
    def some_method(some_arr)
      some_arr.size
    end
  • 当分配默认值给方法参数的时候,在=附近使用空格。
    # bad
    def some_method(arg1=:default, arg2=nil, arg3=[])
      # do something...
    end
    
    # good
    def some_method(arg1 = :default, arg2 = nil, arg3 = [])
      # do something...
    end
  • 避免在不需要的时候使用行连接符(\\)。实际上应该避免行连接符。
    # bad
    result = 1 - \
             2
    
    # good (but still ugly as hell)仍然像地狱一样丑陋
    result = 1 \
             - 2
  • 使用=返回一个表达式的值是很好的,但是需要用括号把赋值运算式括起来。
    # good - show intented use of assignment
    if (v = array.grep(/foo/)) ...
    
    # bad
    if v = array.grep(/foo/) ...
    
    # also good - show intended use of assignment and has correct precedence.
    if (v = self.next_value) == "hello" ...
  • 使用||=轻松的初始化变量。
    # set name to Vozhidar, only if it's nil or false
    name ||= 'Bozhidar'
  • 不要使用||=来初始化布尔变量。(思考一些如果当前值为false的时候会发生什么。)
    # bad - would set enabled to true even if it was false
    enable ||= true
    
    # good
    enabled = true if enabled.nil?
  • 避免使用Perl的指定变量风格(比如,$0-9$,等等。)。它们相当神秘,不鼓励在单行代码之外使用它们。
  • 从来不要在方法名和(参数)开括号之间使用空格。
    # bad
    f (3+2) + 1
    
    # good
    f(3 + 2) +1
  • 如果方法的第一个参数以开括号开始,通常使用括号把它们全部括起来。例如f((3 + 2) + 1)
  • 通常使用-w 选项运行Ruby解释器,在你忘记上面所诉规则,ruby将会提示你。
  • 当你的hash字典是symbols的时候,使用Ruby 1.9的字面量语法。
    # bad
    hash = { :one => 1, :two => 2 }
    
    #good
    hash = { one: 1, two: 2 }
  • 使用新的 lambda 语法。
    # bad
    lambda = lambda { |a, b| a + b }
    lambda.call(1, 2)
    
    # good
    lambda = ->(a, b) { a + b }
    lambda.(1, 2)
  • 对不使用的块变量使用_
    # bad
    result = hash.map { |k, v| v + 1}
    
    # good
    result = hash.map { |_, v| v + 1 }

命名

The only real difficulties in programming are cache invalidation and naming things.  – Phil Karlton 程序(运行)中唯一不一样的是无效的缓存和命名的事物(变量)。 – Phil Karlton
  • 使用snake_case的形式给变量和方法命名。
  • Snake case: punctuation is removed and spaces are replaced by single underscores. Normally the letters share the same case (either UPPER_CASE_EMBEDDED_UNDERSCORE or lower_case_embedded_underscore) but the case can be mixed
  • 使用CamelCase(駝峰式大小寫)的形式给类和模块命名。(保持使用缩略首字母大写的方式如HTTP, RFC, XML)
  • 使用SCREAMING_SNAKE_CASE给常量命名。
  • 在表示断言的方法名(方法返回真或者假)的末尾添加一个问号(如Array#empty?)。
  • 可能会造成潜在“危险”的方法名(如修改self或者在原处修改变量的方法,exit!等)应该在末尾添加一个感叹号。
  • 当在短的块中使用reduce时,命名参数|a, e| (accumulator, element)。
    #Combines all elements of enum枚举 by applying a binary operation, specified by a block or a symbol that names a method or operator.
    # Sum some numbers
    (5..10).reduce(:+)                            #=> 45#reduce
    # Same using a block and inject
    (5..10).inject {|sum, n| sum + n }            #=> 45 #inject注入
    # Multiply some numbers
    (5..10).reduce(1, :*)                         #=> 151200
    # Same using a block
    (5..10).inject(1) {|product, n| product * n } #=> 151200
  • 在定义二元操作符方法时,将其的参数取名为other。
    def +(other)
      # body omitted
    end
  • map优先于collectfind优先于detectselect优先于find_allreduce优先于injectsize优先于length。以上的规则并不绝定,如果使用后者能提高代码的可读性,那么尽管使用它们。这些对应的方法名(如collect,detect,inject)继承于SmallTalk语言,它们在其它语言中并不是很通用。鼓励使用select而不是find_all是因为select与reject一同使用时很不错,并且它的名字具有很好的自解释性。

注释

Good code is its own best documentation. As you’re about to add a comment, ask yourself, “How can I improve the code so that this comment isn’t needed?” Improve the code and then document it to make it even clearer.  – Steve McConnell 好的代码在于它有好的文档。当你打算添加一个注释,问问自己,“我该做的是怎样提高代码质量,那么这个注释是不是不需要了?”提高代码并且给他们添加文档使得它更加简洁。 – Steve McConnell
  • 写出自解释文档代码,然后忽略不工作的这部分。这不是说着玩。
  • 注释长于一个单词则以大写开始并使用标点。使用一个空格将注释与符号隔开。Use one space after periods.
  • 避免多余的注释。
    # bad
    counter += 1 # increments counter by one
  • 随时更新注释,没有注释比过期的注释更好。
  • 不要为糟糕的代码写注释。重构它们,使它们能够“自解释”。(Do or do not - there is no try.)

注解

  • 注解应该写在紧接相关代码的上方。
  • 注解关键字后跟一个冒号和空格,然后是描述问题的记录。
  • 如果需要多行来描述问题,随后的行需要在#后面缩进两个空格。
    def bar
      # FIXME: This has crashed occasionally since v3.2.1. It may
      #  be related to the BarBazUtil upgrade.
      baz(:quux)
    end
  • 如果问题相当明显,那么任何文档就多余了,注解也可以(违规的)在行尾而没有任何备注。这种用法不应当在一般情况下使用,也不应该是一个rule。
    def bar
      sleep 100 # OPTIMIZE
    end
  • 使用TODO来备注缺失的特性或者在以后添加的功能。
  • 使用FIXME来备注有问题需要修复的代码。
  • 使用OPTIMIZE来备注慢的或者低效的可能引起性能问题的代码。
  • 使用HACK来备注那些使用问题代码的地方可能需要重构。
  • 使用REVIEW来备注那些需要反复查看确认工作正常的代码。例如:REVIEW: 你确定客户端是怎样正确的完成X的吗?
  • 使用其他自定义的关键字如果认为它是合适的,但是确保在你的项目的README或者类似的地方注明。

  • 在设计类层次的时候确保他们符合Liskov Substitution Principle原则。(译者注: LSP原则大概含义为: 如果一个函数中引用了`父类的实例’, 则一定可以使用其子类的实例替代, 并且函数的基本功能不变. (虽然功能允许被扩展)) >Liskov替换原则:子类型必须能够替换它们的基类型 > 1. 如果每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换为o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。 > 2. 换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别。只有衍生类替换基类的同时软件实体的功能没有发生变化,基类才能真正被复用。 > 3. 里氏代换原则由Barbar Liskov(芭芭拉.里氏)提出,是继承复用的基石。 > 4. 一个继承是否符合里氏代换原则,可以判断该继承是否合理(是否隐藏有缺陷)。
  • 努力是你的类尽可能的健壮SOLID
  • 总是为你自己的类提供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
  • 考虑添加工厂方法来提供灵活的方法来创建实际类实例。
    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! Quack'
      end
    end
    
    # extend superclass
    class Dog < Animal
      def speak
        puts 'Bau! Bau!'
      end
    end
    
    # good
    class Duck
      def speak
        puts 'Quack! Quack'
      end
    end
    
    class Dog
      def speak
        puts 'Bau! 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"
    正如上例看到的, 所有的子类共享类变量, 并且可以直接修改类变量,此时使用类实例变量是更好的主意.
  • 根据方法的用途为他们分配合适的可见度( privateprotected ),不要让所有的方法都是 public (这是默认设定)。这是 Ruby 不是 *Python*。
  • publicprotected, 和 private 等可见性关键字应该和其(指定)的方法具有相同的缩进。并且不同的可见性关键字之间留一个空格。
    class SomeClass
      def public_method
        # ...
      end
    
      private
      def private_method
        # ...
      end
    end
  • 使用self来定义单例方法. 当代码重构时, 这将使得方法定义代码更加具有灵活性.
    class TestClass
      # bad
      def TestClass.some_method
        # body omitted
      end
    
      # good
      def self.some_other_method
        # body ommited
      end
    
      # 也可以这样方便的定义多个单例方法。
      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 方法,因此兩者的行為會不一樣。只給予單一物件的方法稱為单例方法 (singleton method)。

异常处理

  • 不要抑制异常输出。
    begin
      # an exception occurs here
    rescue SomeError
      # the rescue clause does absolutely nothing还没有补救代码
    end
  • 不要用异常来控制流。
    # bad
    begin
      n / d
    rescue ZeroDivisionError
      puts "Cannot divide by 0!"
    end
    
    # good
    if n.zero?
      puts "Cannot divide by 0!"
    else
      n / d
  • 应该总是避免拦截(最顶级的)Exception异常类.
    # bad 
    begin
      # an exception occurs here
    rescue
      # exception handling
    end
    
    # still bad
    begin
      # an exception occurs here
    rescue Exception
      # exception handling
    end
  • 将更具体的异常放在拦截链的上方,否则他们将不会被捕获。
    # bad
    begin
      # some code
    rescue Exception => e
      # some handling
    rescue StandardError => e
      # some handling
    end
    
    # good
    begin
      # some code
    rescue StandardError => e
      # some handling
    rescue Exception => e
      # some handling
    end
  • 使用ensure语句, 来确保总是执行一些特地的操作.
    f = File.open("testfile")
    begin
      # .. process
    rescue
      # .. handle error
    ensure
      f.close unless f.nil?
    end
  • 除非必要, 尽可能使用Ruby标准库中异常类,而不是引入一个新的异常类。(而不是派生自己的异常类)

集合

  • 总是使用%w的方式来定义字符串数组.(译者注: w表示英文单词word, 而且定义之间千万不能有逗号)
    # bad
    STATES = ['draft', 'open', 'closed']
    
    # good
    STATES = %w(draft open closed)
  • 避免直接引用靠后的数组元素, 这样隐式的之前的元素都被赋值为nil.
    arr = []
    arr[100] = 1 # now you have an array with lots of nils
  • 如果要确保元素唯一, 则使用 Set 代替 Array .Set 更适合于无顺序的, 并且元素唯一的集合, 集合具有类似于数组一致性操作以及哈希的快速查找.
  • 尽可能使用符号代替字符串作为哈希键.
    # bad
    hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
    
    # good
    hash = { one: 1, two: 2, three: 3 }
  • 避免使用易变对象作为哈希键。
  • 优先使用1.9的新哈希语法。
    # bad
    hash = { :one => 1, :two => 2, :three => 3 }
    
    # good
    hash = { one: 1, two: 2, three: 3 }
  • 记住, 在Ruby1.9中, 哈希的表现不再是无序的. (译者注: Ruby1.9将会记住元素插入的序列)
  • 当遍历一个集合的同时, 不要修改这个集合。

字符串

  • 优先使用 字符串插值 来代替 字符串串联
    # bad
    email_with_name = user.name + ' <' + user.email + '>'
    
    # good
    email_with_name = "#{user.name} <#{user.email}>"
  • 当不需要使用 字符串插值 或某些特殊字符时, 应该优先使用单引号.
    # bad
    name = "Bozhidar"
    
    # good
    name = 'Bozhidar'
  • 当使用字符串插值替换 实例变量 时, 应该省略{}.
    class Person
      attr_reader :first_name, :last_name
    
      def initialize(first_name, last_name)
        @first_name = first_name
        @last_name = last_name
      end
    
      # bad
      def to_s
        "#{@first_name} #{@last_name}"
      end
    
      # good
      def to_s
        "#@first_name #@last_name"
      end
    end
  • 操作较大的字符串时, 避免使用 String#+ , 如果需要修改被操作字符串, 应该总是使用 String#<< 作为代替。就地并列字符串实例变体比 String#+ 更快,它创建了多个字符串对象。
    # good and also fast
    html = ''
    html << '<h1>Page title</h1>'
    
    paragraphs.each do |paragraph|
      html << "<p>#{paragraph}</p>"
    end

正则表达式

  • 如果只是需要中查找字符串的 text, 不要使用正则表达式:string['text']
  • 针对简单的结构, 你可以直接使用string[/RE/]的方式来查询.
    match = string[/regexp/]             # get content of matched regexp
    first_group = string[/text(grp)/, 1] # get content of captured group
    string[/text (grp)/, 1] = 'replace'  # string => 'text replace'
  • 当无需引用分组内容时, 应该使用(?:RE)代替(RE).
    /(first|second)/   # bad
    /(?:first|second)/ # good
  • 避免使用 $1-$9 风格的分组引用, 而应该使用1.9新增的命名分组来代替.
    # bad
    /(regexp)/ =~ string
    ...
    process $1
    
    # good
    /(?<meaningful_var>regexp)/ =~ string
    ...
    process meaningful_var
  • 字符类有以下几个特殊关键字值得注意: ^-\], 所以, 不要在集合中, 转义 . 或者 [] 中的括号, 他们是正常字符.
  • 注意, ^ 和 $ , 他们匹配行首和行尾, 而不是一个字符串的结尾, 如果你想匹配整个字符串, 用 \A 和 \Z
    string = "some injection\nusername"
    string[/^username$/]   # matches
    string[/\Ausername\Z/] # don't match
  • 使用 x 修饰符来匹配复杂的表达式, 这将使得RE更具可读性, 你可以添加一些有用的注释. 注意, 所有空格将被忽略.
    regexp = %r{
      start         # some text
      \s            # white space char
      (group)       # first group
      (?:alt1|alt2) # some alternation
      end
    }x
  • sub/gsub也支持哈希以及代码块形式语法, 可用于复杂情形下的替换操作.

百分号和字面值

  • 多用 %w
    STATES = %w(draft open closed)
  • 定义需要插值和嵌套双引号符号的单行字符串,使用%()的方式.而多行字符串, 尽量使用heredocs格式.
    # bad (不需要插值)
    %(<div>Some text</div>)
    # should be '<div>Some text</div>' # 应该这样写
    
    # bad (没有双引号)
    %(This is #{quality} style)
    # should be "This is #{quality} style" # 应该这样写
    
    # bad (multiple lines)
    %(<div>\n<span>#{exclamation}</span>\n</div>)
    # should be a heredoc.
    
    # good (插值, 引号, 单行)
    %(<tr><td>#{name}</td>)
    Heredoc is a robust way to create string in PHP with more lines but without using quotations. Heredoc 是 php 中不使用引号就可以创建多行字符串的一种强大的方式。 line-oriented string literals (Here document) There’s a line-oriente form of the string literals that is usually called as here document. Following a << you can specify a string or an identifier to terminate the string literal, and all lines following the current line up to the terminator are the value of the string. If the terminator is quoted, the type of quotes determines the type of the line-oriented string literal. Notice there must be no space between << and the terminator . If the - placed before the delimiter, then all leading whitespcae characters (tabs or spaces) are stripped from input lines and the line containing delimiter. This allows here-documents within scripts to be indented in a natural fashion.
      print <<EOF
        The price is #{$Price}.
        EOF
    
      print <<"EOF";            # same as above
    The price is #{$Price}.
    EOF
    
      print <<`EOC`         # execute commands
    echo hi there
    echo lo there
    EOC
    
      print <<"foo", <<"bar"    # you can stack them
    I said foo.
    foo
    I said bar.
    bar
    
      myfunc(<<"THIS", 23, <<'THAT')
    Here's a line
    or two.
    THIS
    and here's another.
    THAT
    
      if need_define_foo
        eval <<-EOS         # delimiters can be indented
          def foo
            print "foo\n"
          end
        EOS
      end
  • %r 的方式只适合于定义包含多个 / 符号的正则表达式。
    # bad
    %r(\s+)
    
    # still bad
    %r(^/(.*)$)
    # should be /^\/(.*)$/
    
    # good
    %r(^/blog/2011/(.*)$)
    irb(main):001:0> string="asdfas.64"
    => "asdfas.64"
    irb(main):002:0> string[/^\/(.*)$/]
    => nil
    irb(main):003:0> string="/asdfas.64"
    => "/asdfas.64"
    irb(main):004:0> string[/^\/(.*)$/]
    => "/asdfas.64"
    irb(main):007:0> string="/blog/2011/asdfas.64"
    => "/blog/2011/tmp/asdfas.64"
    irb(main):008:0> string[%r(^/blog/2011/(.*)$)]
    => "/blog/2011/tmp/asdfas.64"
  • 避免使用%q%Q, %x, %s,和 %W
  • 优先使用()作为%类语法格式的分隔符.(译者注, 本人很喜欢 %(...), 不过Programming Ruby中, 显然更喜欢使用%{}的方式)

元编程

  • 在编写库时,不要乱动核心库。(不要画蛇添足)

杂项

  • 总是打开Ruby -w开关。
  • 通常情况下, 尽量避免使用哈希作为方法的 optional 参数. (此时应该考虑这个方法是不是功能太多?)
  • 避免一个方法内容超过10行代码, 理想情况下, 大多数方法内容应该少于5行.(不算空行)
  • 尽量避免方法的参数超过三或四个.
  • 有时候, 必须用到全局方法, 应该增加这些方法到 Kernel 模块,并设置他们可见性关键字为 private
  • 尽可能使用类实例变量代替全局变量. (译者注:是类实例变量, 而不是类的实例变量. 汗~~)
    #bad
    $foo_bar = 1
    
    #good
    class Foo
      class << self
        attr_accessor :bar
      end
    end
    
    Foo.bar = 1
  • 能够用 alias_method 就不要用 alias
  • 使用 OptionParser 来解析复杂的命令行选项, 较简单的命令行, -s 参数即可。
  • 按照功能来编写方法, 当方法名有意义时, 应该避免方法功能被随意的改变。
  • 避免不需要的元编程。
  • 除非必要, 避免更改已经定义的方法的参数。
  • 避免超过三级的代码块嵌套。
  • 应该持续性的遵守以上指导方针。
  • 多使用(生活)常识。

Contributing

Nothing written in this guide is set in stone. It’s my desire to work together with everyone interested in Ruby coding style, so that we could ultimately create a resource that will be beneficial to the entire Ruby community.

Feel free to open tickets or send pull requests with improvements. Thanks in advance for your help!

Spread the Word

A community-driven style guide is of little use to a community that doesn’t know about its existence. Tweet about the guide, share it with your friends and colleagues. Every comment, suggestion or opinion we get makes the guide just a little bit better. And we want to have the best possible guide, don’t we?

rails中的业务处理Active Record Transactions

rails中的业务处理Active Record Transactions

Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and vice versa.

Transactions是保护行的代码块,用于只有你能够完成所有的元操作才permanent SQL statements(执行sql语句)。经典的例子是一个在两个帐号之间的业务,这里你只能在转账成功后才能有一个deposit存款,反之也是。

Transactions enforce the integrity of the database and guard the data against program errors or database break-downs. So basically you should use transaction blocks whenever you have a number of statements that must be executed together or not at all.

Transactions 保障了数据库的有效性,能够防止程序错误或者数据库故障对数据的影响。因此基本上你应该使用Transactions代码块无论何时你需要一系列的声明必须在一起执行或者什么都不做。

For example:

ActiveRecord::Base.transaction do
  david.withdrawal(100)
  mary.deposit(100)
end

This example will only take money from David and give it to Mary if neither withdrawal nor deposit raise an exception. Exceptions will force a ROLLBACK that returns the database to the state before the transaction began. Be aware, though, that the objects will not have their instance data returned to their pre-transactional state.

这个例子仅仅从David那里转账一些钱给Mary如果汇款或者存款都没有异常抛出。意外情况发生则会强制一个回滚数据库到业务开始的状态。请保持清醒,这样,对象将不会有实例数据回到到它们的每一个业务来源地。

Different Active Record classes in a single transaction

Though the transaction class method is called on some Active Record class, the objects within the transaction block need not all be instances of that class. This is because transactions are per-database connection, not per-model.

即使transaction类方法被一些的Active Record class调用,在transaction代码块中的对象并不需要所有的实例都是来自那个类。这是因为transaction是基于每个数据连接的而不是每个model。

In this example a balance record is transactionally saved even though transaction is called on the Account class:

在这个例子中余额记录被transactionally保存即使transaction也被Account类调用。

Account.transaction do
  balance.save!
  account.save!
end

The transaction method is also available as a model instance method. For example, you can also do this:

transaction在model实例方法中也是可用的。例如你可以这样做:

balance.transaction do
  balance.save!
  account.save!
end

Transactions are not distributed across database connections

A transaction acts on a single database connection. If you have multiple class-specific databases, the transaction will not protect interaction among them. One workaround is to begin a transaction on each class whose models you alter:

一个transaction动作是一个单个的数据库连接。如果你的数据库指定了多个类,transaction将不会保护他们全部的相互影响。一个解决办法是开始一个transaction包含你要改变的model的每一个类:

Student.transaction do
  Course.transaction do
    course.enroll(student)
    student.units += course.units
  end
end

This is a poor solution, but fully distributed transactions are beyond the scope of Active Record.

这是一个无赖的办法,但是完整的区域业务超出了Active Record的范围。

save and destroy are automatically wrapped in a transaction

Both save and destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks will happen under its protected cover. So you can use validations to check for values that the transaction depends on or you can raise exceptions in the callbacks to rollback, including after_* callbacks.

保存和删除都是包装在一个业务中的确保无论你何时做验证或者回调都会在它的保护下。因此你可以使用验证来检查业务中的值在此基础上或者你可以在回调中抛出异常来回滚,包含after_* callbacks。

As a consequence changes to the database are not seen outside your connection until the operation is complete. For example, if you try to update the index of a search engine in after_save the indexer won’t see the updated record. The after_commit callback is the only one that is triggered once the update is committed. See below.

这样有一个后果就是在你的操作完成之前,数据库的改变都不会表现出来(不会突出于你的连接之外)。例如,如果你尝试使用after_save更新一个搜索引擎的索引,索引者将不会发现更新的记录。after_commit回调是仅有的一个它会在一旦更新完成就被触发。看下面。

Exception handling and rolling back

Exception handling and rolling back

Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you should be ready to catch those in your application code.

同样也要留心在一个业务代码块中的异常抛出将会被传播(在触发回调之后)。因此你应该准备好在应用程序中抓取这些异常。

One exception is the ActiveRecord::Rollback exception, which will trigger a ROLLBACK when raised, but not be re-raised by the transaction block.

Warning: one should not catch ActiveRecord::StatementInvalid exceptions inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an error occurred at the database level, for example when a unique constraint is violated. On some database systems, such as PostgreSQL, database errors inside a transaction cause the entire transaction to become unusable until it’s restarted from the beginning. Here is an example which demonstrates the problem:

# Suppose that we have a Number model with a unique column called 'i'.
Number.transaction do
  Number.create(:i => 0)
  begin
    # This will raise a unique constraint error...
    Number.create(:i => 0)
  rescue ActiveRecord::StatementInvalid
    # ...which we ignore.
  end

  # On PostgreSQL, the transaction is now unusable. The following
  # statement will cause a PostgreSQL error, even though the unique
  # constraint is no longer violated:
  Number.create(:i => 1)
  # => "PGError: ERROR:  current transaction is aborted, commands
  #     ignored until end of transaction block"
end

One should restart the entire transaction if an ActiveRecord::StatementInvalid occurred.

应该重新开始业务如果ActiveRecord::StatementInvalid发生了。

Nested transactions

transaction calls can be nested. By default, this makes all database statements in the nested transaction block become part of the parent transaction. For example, the following behavior may be surprising:

transaction可以嵌套调用。默认情况下,这将会将被嵌套的transaction作为其中所有的数据库声明的父业务。

User.transaction do
  User.create(:username => 'Kotori')
  User.transaction do
    User.create(:username => 'Nemu')
    raise ActiveRecord::Rollback
  end
end

creates both “Kotori” and “Nemu”. Reason is the ActiveRecord::Rollback exception in the nested block does not issue a ROLLBACK. Since these exceptions are captured in transaction blocks, the parent block does not see it and the real transaction is committed.

In order to get a ROLLBACK for the nested transaction you may ask for a real sub-transaction by passing :requires_new => true. If anything goes wrong, the database rolls back to the beginning of the sub-transaction without rolling back the parent transaction. 如果有任何错误,数据库将会回滚到子业务的开始状态,并没有回滚父业务。If we add it to the previous example:

User.transaction do
  User.create(:username => 'Kotori')
  User.transaction(:requires_new => true) do
    User.create(:username => 'Nemu')
    raise ActiveRecord::Rollback
  end
end

only “Kotori” is created. (This works on MySQL and PostgreSQL, but not on SQLite3.)

Most databases don’t support true nested transactions. At the time of writing, the only database that we’re aware of that supports true nested transactions, is MS-SQL. Because of this, Active Record emulates nested transactions by using savepoints on MySQL and PostgreSQL. See dev.mysql.com/doc/refman/5.0/en/savepoint.html for more information about savepoints.

Callbacks

There are two types of callbacks associated with committing and rolling back transactions: after_commit and after_rollback.

after_commit callbacks are called on every record saved or destroyed within a transaction immediately after the transaction is committed. after_rollback callbacks are called on every record saved or destroyed within a transaction immediately after the transaction or savepoint is rolled back.

These callbacks are useful for interacting with other systems since you will be guaranteed that the callback is only executed when the database is in a permanent state. For example, after_commit is a good spot to put in a hook to clearing a cache since clearing it from within a transaction could trigger the cache to be regenerated before the database is updated.

Caveats

If you’re on MySQL, then do not use DDL operations in nested transactions blocks that are emulated with savepoints. That is, do not execute statements like ‘CREATE TABLE’ inside such blocks. This is because MySQL automatically releases all savepoints upon executing a DDL operation. When transaction is finished and tries to release the savepoint it created earlier, a database error will occur because the savepoint has already been automatically released. The following example demonstrates the problem:

Model.connection.transaction do                           # BEGIN
  Model.connection.transaction(:requires_new => true) do  # CREATE SAVEPOINT active_record_1
    Model.connection.create_table(...)                    # active_record_1 now automatically released
  end                                                     # RELEASE savepoint active_record_1
                                                          # ^^^^ BOOM! database error!
end

Note that “TRUNCATE” is also a MySQL DDL statement!

Ruby变量

ruby变量

变量,实例变量,类变量,甚至还有”常量”其实都只是对象引用。它们引用对象,但是它们并不是对象本身。因此,它们可以被动态地改变,甚至引用另一种不同类型的对象。

  因为这一灵活性,所以必须在Ruby中进行一些约定以帮助每个人都知道某个变量正为代码所使用。其实,你已经看到了其中之一(@符号,它意味着这是一个实例变量)。其它的变量,方法和类命名约定列于下表1中。

  · 局部变量和方法参数以一个小写字母开头。   · 方法名字以一个小写字母开头。   · 全局变量以一个$开头。   · 实例变量以一个@开头。   · 类变量以两个@开头。   · 常数以一个大写字母开头(它们经常被指定全部大写)。   · 类和模块名以一个大写字母开头。

局部变量 全局变量 实例变量 类变量 常数 类名 方法名
aVar $Var @var @@var VAR MyClass myMethod
name $debug @lastName @@interest PI Rectangle area

        表1.这个表包含了在Ruby编码约定下的相关示例

OO设计原则总结

OO设计原则总结

 设计原则是基本的工具,应用这些规则可以使你的代码更加灵活,更容易维护,更容易扩展。
基本设计原则
封装变化(Encapsulate what varies
面向接口编程而非实现(code to an interface rather than to an implementation)
优先使用组合而非继承(favor Composition over inheritance)
SRP(Single responsibility Principle
    单一职责。系统中的每一个对象都应该只有1个单独的职责,而所有对象所关注的就是自身职责的完成。
    每一个职责都是一个设计的变因,需求变化的时候,需求变化反映为类的职责的变化。当你系统里的对象都只有一个变化的原因时,你就已经很好的遵循了SRP原则了。如果一个类承担了过多的职责,就等于把这些职责耦合在一起了。一个职责的变化就可能消弱或者抑制这个类其它的职责的能力。这种设计会导致脆弱的设计。当发生变化时,设计会遭到意想不到的破坏。
    SRP让这个系统更容易管理和维护,因为不是所有的问题都耦合在一起。
    内聚(Cohesion)其实是SRP原则的另外一个名字。你写了高内聚的软件其实就是很好的应用了SRP原则。 
DRY(Don’t repeat yourself Principle)
    不要重复自己的工作。通过抽取公共部分放置在一个地方来避免重复的代码或功能实现。
    DRY确保women代码容易维护和复用。确保每一个需求和功能在你的系统中只实现一次,否则就存在浪费!系统的用例不存在交集,所以我们的代码更不应该重复。从这个角度看DRY就不只是在说代码了。DRY关注的是系统内的信息和行为都放在一个单一的,明显的位置。
    DRY原则:如何对系统职能进行良好的分割!职责清晰的界限一定程度上保证了代码的单一性。
    
OCP(Open-Close Principle)
    开闭原则OCP关注的是灵活性,改动是通过增加代码进行的,而不是改动现有的代码。
    OCP的应用限定在可能会发生的变化上,通过创建抽象来隔离以后发生的同类变化。
    OCP传递这样一个思想:一旦你写出来可以工作的代码,就要努力保证这段代码可以一直工作。这就成了你的编码的一个底线。一旦我们的代码质量到了一个水平,我们就要尽最大努力保证代码质量不回退。这样就要求我们面对一个问题的时候不会使用凑活的方法来解决,或者说放任自流的方式来解决一个问题(比如:代码添加了无数对特定数据的处理,特化的代码越来越多,代码意图开始含糊不清,这就开始质量退化了。)
    OCP背后的机制:封装和抽象。封闭是建立在抽象的基础上的,使用抽象获得显示的封闭。继承是OCP最简单的例子。除了子类化和方法重载我们还有一些更优雅的方法来实现比如组合。
    那么如何在不改变源代码(关闭修改)的情况下更改它的行为呢?答案就是抽象。
    正确的做法就是开发人员仅对频繁变化的部分做出抽象。拒绝不成熟的抽象,这和抽象本身一样的重要
    OCP是OOD很多说法的核心,如果这个原则有效的应用,我们可以获得更强的可维护性 可重用性 灵活性 健壮性。。然而LSP是OCP成为可能的主要原则之一。
LSP(Liskov substitution Principle)
子类必须能够替换基类。LSP关注的是怎样良好的使用继承。必须清楚是使用一个Method还是要扩展它,但是绝对不是改变它。
        LSP让我们得出一个重要的结论:一个模型如果孤立的看,并不具有真正意义的有效性,模型的有效性只能通过它的客户程序来表现。必须根据设计的使用者做出的合理假设来审视它。而假设是难以预测的,知道设计臭味出现的时候才处理他们。
DIP(Dependency-Inversion Principle)
依赖反转/依赖倒置高层模块不依赖底层模块 两者都应只依赖于抽象。
    抽象不依赖于细节, 而细节依赖于抽象。
    高层模块:包含了应用程序中重要的策略选择和业务模型。这些高层模块使其所在的应用程序区别于其他。
框架设计的核心原则: 如果高层模块依赖底层模块,那么在不同的上下文中重用高层模块会变得十分困难。然而,如果高层模块不依赖于底层模块,那么高层模块就可以非常容易的被重用。
    这里的倒置不仅仅指依赖关系的倒置同时也是接口所有权的倒置。
   Hollywood原则: Don’t call us。 we will call you. 底层模块实现了在高层模块声明并被高层模块调用的接口。
   DIP的简单的启发规则:依赖于抽象。程序汇总所有的依赖都应依赖于抽象类或接口。
   如果一个类很稳定,那么依赖于它不会造成伤害。然而我们自己的具体类大多是不稳定的,通过把他们隐藏在抽象接口后面可以隔离不稳定性。
   
   依赖倒置可以应用于任何一个类向另一个类发送消息的地方。。。(还不是很理解)
   依赖倒置原则是实现许多面向对象技术多宣称的好处的基本底层机制,是面向对象的标志所在。   
ISP(Interface Segregation Principle)
接口隔离原则
使用多个专门的接口比使用一个单一的接口总要好:从一个客户类的角度来讲,一个类对另外一个类的依赖性应当是建立在最小的接口上。
如果接口不是高内聚的,一个接口可以分成N组方法,那么这个接口就需要使用ISP来处理一下了~~。
        一个接口中包含了太多的行为时候,导致他们的客户程序之间产生不正常的依赖关系,我们要做的就是分离接口,实现解耦。使用了ISP后客户程序看到的是多个内聚的接口。

 

[進階]使用 Facade Pattern 取代 Model Callbacks by Xdite

[進階]使用 Facade Pattern 取代 Model Callbacks by xdite

What is “callbacks”?

Rails 的 ActiveRecord 提供了相當方便的 callbacks,能讓開發者在寫 Controller 時,能夠寫出更加 DRY 的程式碼:

  • before_crearte
  • before_save
  • after_create
  • after_save

在從前,在 Controller 裡面想要再 object 儲存之後 do_something,直觀的思路會是這樣:

class PostController   def create     @post = Post.new(params[:post])     @post.save     @post.do_something     redirect_to posts_path   end end 

當時的最佳模式:通常是建議開發者改用 callbacks 或者是 Observer 模式實作。避免 controller 的髒亂。

  • callbacks : after_create

或者是使用 Observer

class PostController < ApplicationController   def create     @post = Post.new(params[:post])     @post.save     redirect_to posts_path   end end class PostObserver < ActiveRecord::Observer   def after_create(post)     post.do_something   end end class Post < ActiveRecord::Base   protected   def do_something   end end

使用 callbacks 所產生的問題

callbacks 雖然很方便,但也產生一些其他的問題。若這個 do_something 是很輕量的 db update,那這個問題還好。但如果是很 heavy 的 hit_3rd_party_api 呢?

在幾個情形下,開發者會遇到不小的麻煩。

  • Model 測試:每次在測試時都會被這個 3rd_party_api 整到,因為外部通訊很慢。
  • do_something_api 是很 heavy 的操作:每次寫測試還是會被很慢的 db query 整到。
  • do_something_api 是很輕微的 update:但是綁定 after_save 操作,在要掃描資料庫,做大規模的某欄位修改時,會不小心觸發到不希望引發的 callbacks,造成不必要的效能問題。

當然,開發者還是可以用其他招數去閃開:

比如說若綁定 after_save 。

可以在 do_somehting 內加入對 dirty object 的偵測,避免被觸發:

 def do_somthing   # 資料存在,且變動的欄位包括 content   if presisited? && changed.include?(“content”)     the_real_thing   end  end

 但這一招並不算理想,原因有幾:

  1. 每次儲存還是需要被掃描一次,可能有效能問題。
  2. 寫測試時還是會呼叫到可能不需要引發的 do_somehting。
  3. if xxx && yyy 這個 condiction chain 可能會無限延伸下去。

 Facade Pattern

那麼要怎樣才能解決這個問題呢?其實我們應該用 Facade Pattern 解決這個問題。

設計模式裡面有一招 Facade Pattern,這一招其實是沒有被寫進 Design Pattern in Ruby 中的。Russ Olson 有寫了一篇文章解釋沒有收錄的原因:因為在 Ruby 中,這一招太簡單太直觀,所以不想收錄 XDDD。但他還是在網站上提供當時寫的草稿,供人參考。

What is Facade Pattern?

Facade Pattern 的目的是「將複雜的介面簡化,將複雜與瑣碎的步驟封裝起來,對外開放簡單的介面,讓客戶端能夠藉由呼叫簡單的介面而完成原本複雜的程式演算。」(來源

延伸閱讀: (原創) 我的Design Pattern之旅[5]:Facade Pattern (OO) (Design Pattern) (C/C++)

實際舉例:

在上述的例子中,其實 do_something 有可能只會在 PostController 用到,而非所有的 model 操作都「需要」用到。所以我們 不應該將 do_somehting 丟進 callbacks(等於全域觸發),再一一寫 case 去閃避執行

與其寫在 callbacks 裡。我們更應該寫的是一個 Service Class 將這一系列複雜昂貴的行為包裝起來,以簡單的介面執行。 class PostController < ApplicationController   def create   CreatePostService(params[:post])   redirect_to posts_path   end  end class CreatePostService   def self.create(params)     post = Post.new(params[:post])     post.save     post.do_something_a     post.do_something_b     post.do_something_c   end end  而在寫測試,只需要對 PostCreateService 這個商業邏輯 class 寫測試即可。而 PostController 和 Post Model 就不會被殃及到。

小結

不少開發者討厭測試的原因,不只是「因為」寫測試很麻煩的原因,「跑一輪測試超級久」也是讓大家很不爽的主因之一。

其實不是這些測試框架寫的爛造成「寫測試很麻煩」、「執行測試超級久」。而是另有其他因素。

許多資深開發者逐漸意識到,真正的主因是在於目前 Rails 的 model 的設計,耦合度太高了。只要沾到 db 就慢,偏偏 db 是世界的中心。只是測某些邏輯,搞到不小心觸發其他不需要測的東西。

ActiveRecord 的問題在於,讓開發者太誤以為 ORM = model。其實開發者真正要寫的測試應該是對商業邏輯的測試,不是對 db 進行測試。

所以才會出現了用 Facade Pattern 取代 callbacks 的手法。

其他

MVC 其實有其不足的部份。坦白說,Rails 也不是真正的 MVC,而是 Model2

目前 MVC 其實是不足的,演化下來,開發者會發現 User class 裡面會開始出現這些東西:

  • current_user.buy_book(book)
  • current_user.add_creadit_point(point)

這屬於 User 裡面應該放的 method 嗎?well,你也可以說適合,也可以說不適合。

適合的原因是:其實你也不知道應該放哪裡,這好像是 User 執行的事,跟他有關,那就放這裡好了!不然也不知道要擺哪裡。

不適合的原因是:這是一個「商業購買行為」。不是所有人都會購物啊。這應該是一個商業購買邏輯。但是….也不知道要放在哪啊。

一直到最近,James Copelin 提出了:DCI 去補充了現有的 MVC 的不足,才算勉強解決了目前浮現的這些問題。

DCI ,與本篇談到的 Facade Pattern 算是頗類似的手法。

有關於 DCI ( Data, Context, Interaction ) 的文章,我會在之後發表。我同時也推薦各位去看這方面的主題。這個方向應該會是 Rails 專案設計上未來演化的方向之一。

 

Python Ruby Geek by Django社区and Ruby-china

python ruby geek by django社区and ruby-china

by ruby-china 由 gaicitadie瞎扯淡 节点

统计一个字符串在另一个字符串中出现的次数,python只需要一个count方法:

>>> '11ab1111ab111ac11111'.count('ab') 2
huacnlee 1楼, 于24小时前回复 irb> ‘11ab1111ab111ac11111’.scan(“ab”).count 2 ywencn 2楼, 于24小时前回复 1.9.2p290 :001 >  ‘11ab1111ab111ac11111’.count(‘ab’)  => 5 楼主想表达什么? ywencn 3楼, 于24小时前回复 哎呀。。。怎么python和ruby的count还不一样,哈哈 huacnlee 4楼, 于24小时前回复 Ruby 的 “”.count 统计的是后面所有的字符 dreamrise 5楼, 于24小时前回复 貌似_who还写过一个python与ruby转换的程序? gaicitadie 6楼, 于23小时前回复 奥运奖牌榜: 国家 金牌数 银牌数 铜牌数 china 37 26 11 usa 30 22 50 russia 30 33 20 中国习惯上先按金牌数排名,金牌数一样的按银牌数再排,如果银牌数再一样就按铜牌数排: >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) [(‘china’, 37, 26, 11), (‘russia’, 30, 33, 20), (‘usa’, 30, 22, 50)] 美国习惯上金牌银牌铜牌都是奖牌,所以按奖牌总数排序: >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:-(x[1]+x[2]+x[3])) [(‘usa’, 30, 22, 50), (‘russia’, 30, 33, 20), (‘china’, 37, 26, 11)] python的排序达到了类似SQL查询的能力,只需要告诉它排序的条件就可以了,python为数据而生 gaicitadie 7楼, 于23小时前回复 上面的例子是python模拟SQL的order by功能,下面的例子用python模拟SQL的where条件查询 统计金牌数超过35的国家: >>> [x for x in [(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)] if x[1]>35] [(‘china’, 37, 26, 11)] 统计奖牌总数超过100的国家: >>> [x for x in [(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)] if x[1]+x[2]+x[3]>100] [(‘usa’, 30, 22, 50)] huyong36 8楼, 于23小时前回复 @gaicitadie ruby是 [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] > 35} [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] + x[2] + x[3] > 100} quakewang 9楼, 于23小时前回复 #6楼 @gaicitadie order by 的ruby代码 [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|m| [-m[1], -m[2], -m[3]]} [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|m| -(m[1] + m[2] + m[3])]} skandhas 10楼, 于23小时前回复 从楼主的例子直接翻译到Ruby 1 中国习惯上先按金牌数排名,金牌数一样的按银牌数再排,如果银牌数再一样就按铜牌数排: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|x| [-x[1],-x[2],-x[3]]} 2 美国习惯上金牌银牌铜牌都是奖牌,所以按奖牌总数排序: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|x| -(x[1]+x[2]+x[3])} 3 统计金牌数超过35的国家: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] >35} 4 统计奖牌总数超过100的国家: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1]+x[2]+x[3] > 100} 这两个语言都挺类似,我觉得ruby的select更直观。 另 楼主这个帖子想表达什么?没看出什么来。如果说只通过sorted就说明python是为数据而生的话,那ruby不也是吗。哈哈 daqing 11楼, 于23小时前回复 我来写个Ruby版本的。 第一个,奖牌排序: data = [[:china, 27, 26, 11], [:usa, 20, 22, 50], [:russia, 30, 33, 20]] data.sort_by { |x| [-x[1], -x[2], -x[3]] } # 中国排序方法,按金/银/铜牌数 data.sort_by { |x| -(x[1] + x[2] + x[3]) } # 美国排序方法,按奖牌总数 第二个,奖牌统计: data.select { |x| x[1] > 35 } # 金牌数超过35的国家 data.select { |x| x[1] + x[2] + x[3] > 100 } # 奖牌总数超过100的国家 哪个更简洁,一目了然了吧。 daqing 12楼, 于23小时前回复 原来大家都在回复。。等我写出来才发现。 daqing 13楼, 于23小时前回复 #10楼 @skandhas 看了你的方法,才想到,select是更直接的做法。collect方法会包含nil值。 reus 14楼, 于23小时前回复 Why I Hate Advocacy http://www.perl.com/pub/2000/12/advocacy.html gaicitadie 15楼, 于23小时前回复 总统选举投票,初步唱票记录: [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] 根据唱票记录统计每人的票数并按从多到少排序 >>> l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] >>> sorted(set([(i, l.count(i)) for i in l]), key=lambda x:-x[1]) [(‘Jim’, 4), (‘bush’, 2), (‘obama’, 1)] clearJiang 16楼, 于23小时前回复 #15楼 @gaicitadie 不如直接用collections.Counter gaicitadie 17楼, 于23小时前回复 #16楼 @clearJiang 低版本没有collections daqing 18楼, 于23小时前回复 总统选举投票 l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.uniq.collect { |x| [x, l.count(x)] } => [[“Jim”, 4], [“bush”, 2], [“obama”, 1]] skandhas 19楼, 于22小时前回复 #15楼 @gaicitadie 根据唱票记录统计每人的票数并按从多到少排序: l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.group_by{|i| i}.map{|k,v| [k,v.length] } quakewang 20楼, 于22小时前回复 python要和ruby比 文件、字符操作或者数组、Hash操作的便利性绝对完败,要砸场还不如在性能上一棍子打死ruby。 bony 21楼, 于22小时前回复 这样的帖子应该多一点。长知识。@skandhas cool. quakewang 22楼, 于22小时前回复 #15楼 @gaicitadie [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’].inject(Hash.new(0)) {|h, e| h[e] += 1; h}.sort_by{|e| -e[1]} daqing 23楼, 于22小时前回复 说实话,Python的lambda匿名函数,跟Ruby的Block相比,从书写上就败了。 gaicitadie 24楼, 于22小时前回复 随机设置验证码的4个字符(不包括图片处理部分) >>> import random >>> s = ‘ABCDEFGHIJKLMNPRSTUVWXYZ’ >>> ”.join(random.sample((s),4)) ‘EXSG’ >>> ”.join(random.sample((s),4)) ‘TGYN’ >>> ”.join(random.sample((s),4)) ‘MEYP’ >>> ”.join(random.sample((s),4)) ‘TGIF’ >>> ”.join(random.sample((s),4)) ‘JDWF’ quakewang 25楼, 于22小时前回复 #24楼 @gaicitadie (‘A’..’Z’).to_a.sample(4).join reus 26楼, 于22小时前回复 #15楼 @gaicitadie 你这个算法是O(n ^ 2)的,应该用reduce def stat(acc, x):   acc.setdefault(x, 0)   acc[x] += 1   return acc sorted(reduce(stat,   [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’], {}).iteritems(),     key = lambda x: -x[1]) huyong36 27楼, 于22小时前回复 @skandhas cool,加上个排序。 gaicitadie 28楼, 于22小时前回复 #26楼 @reus reduce不如列表解析快,虽然list.count会重复统计 skandhas 29楼, 于22小时前回复 #27楼 @huyong36 对,是忘了排序 l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.group_by{|i| i}.map{|k,v| [k,v.length] }.sort_by{|name,count| -count } huacnlee 30楼, 于22小时前回复 风格不同而已,用起来都是一样方便。这个就是让我喜欢 Python 和 Ruby 的原因之一。 reus 31楼, 于22小时前回复 #28楼 @gaicitadie 就是慢在count调用上,for i in l遍历数组,且每个元素又再count遍历一次,O(n ^ 2) reduce只需要遍历一次,O(n) 不信可以测试下 huyong36 32楼, 于22小时前回复 这帖应该是捧场帖,我喜欢这样的学习。 raecoo 33楼, 于22小时前回复 受用 hysios 34楼, 于21小时前回复 •字符串查找 # python >>> ‘11ab1111ab111ac11111’.count(‘ab’) 2 # Ruby ruby-1.9.2-p290 >   ‘11ab111123ab111ac11111’.count ‘ab’, ‘b’ 2 hysios 35楼, 于21小时前回复 •奖牌排序 # python >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) [(‘china’, 37, 26, 11), (‘russia’, 30, 33, 20), (‘usa’, 30, 22, 50)] # ruby ruby-1.9.2-p290 > [[‘china’,37,26,11], [‘usa’,30,22,50],[‘russia’,30,33,20]].sort_by {|name,j,y,t| [-j,-y,-t] }  => [[“china”, 37, 26, 11], [“russia”, 30, 33, 20], [“usa”, 30, 22, 50]] hysios 36楼, 于21小时前回复 •奖牌统计 # python >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:-(x[1]+x[2]+x[3])) [(‘usa’, 30, 22, 50), (‘russia’, 30, 33, 20), (‘china’, 37, 26, 11)] # ruby ruby-1.9.2-p290 > [[‘china’,37,26,11], [‘usa’,30,22,50],[‘russia’,30,33,20]].sort_by {|name,j,y,t| [-j + -y + -t] }  => [[“usa”, 30, 22, 50], [“russia”, 30, 33, 20], [“china”, 37, 26, 11]] kfll 37楼, 于21小时前回复 捧场.. js: ‘11ab1111ab111ac11111’.match(/ab/g).length; ‘11ab1111ab111ac11111’.split(‘ab’).length - 1; 中式排名: [[37, 26, 11], [30, 22, 50], [30, 33, 20]].sort().reverse(); hysios 38楼, 于21小时前回复 总统选举投票,初步唱票记录: # ruby >>> l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] >>> sorted(set([(i, l.count(i)) for i in l]), key=lambda x:-x[1]) [(‘Jim’, 4), (‘bush’, 2), (‘obama’, 1)] # ruby ruby-1.9.2-p290 >  [‘bush’,’Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’].each_with_object({}) {|name,s| s[name] = s.fetch(name,0) + 1 }.sort  => [[“Jim”, 4], [“bush”, 3], [“obama”, 1]] huyong36 39楼, 于21小时前回复 #34楼 @hysios ‘11ab111123ab111ac11111’.count ‘ab’, ‘b’ 这样不对吧…这样只是找出来字符串里出现b的次数。 irb(main):106:0> ‘11ab111123ab111ac11111b’.count ‘ab’, ‘b’ => 3 hysios 40楼, 于21小时前回复 @huyong36 thx count是没办法实现的, 别的方法也不错 huyong36 41楼, 于20小时前回复 #40楼 @hysios 恩,请教 irb(main):115:0> ‘11ab111123ab111c11111’.count  ‘a’ => 2 irb(main):114:0> ‘11ab111123ab111c11111’.count  ‘ab’ => 4 字符可以统计,为什么字符串不能。。 jhjguxin 42楼, 于20小时前回复 @huyong36 count([other_str]+) → fixnum click to toggle source Each other_str parameter defines a set of characters to count. The intersection of these sets defines the characters to count in str. Any other_str that starts with a caret (^) is negated. The sequence c1–c2 means all characters between c1 and c2. Guest 43楼, 于20小时前回复 gaicitadie = [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].tap do |man|   def man.make_self(&process); process.call self; end   def man.become_egghead     `python -c “print( sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) )”`   end   def man.glow_up     dont_be_shy = true     unless self.respond_to? :more_elegant, dont_be_shy       def self.more_elegant         self.sort_by { |country, glods, silvers, bronzes| [-glods,-silvers,-bronzes] }       end     end     if self.respond_to? :become_egghead       class << self; remove_method :become_egghead; end     end     self   end end gaicitadie.make_self &:become_egghead gaicitadie.glow_up.make_self &:more_elegant geekontheway 44楼, 于20小时前回复 #41楼 @huyong36 ruby的count 统计的是字符的数量 所以’11ab111123ab111c11111’.count ‘ab’等同于’11ab111123ab111c11111’.count ‘a’ + ‘11ab111123ab111c11111’.count ‘b’ jhjguxin 45楼, 于20小时前回复 简而言之就是 取每一个字符的count的交集 huyong36 46楼, 于20小时前回复 @geekontheway @jhjguxin 3Q… hysios 47楼, 于20小时前回复 #41楼 @huyong36 count 是统计所有的字符,并不会把参数当成字符串处理 FenRagwort 48楼, 于17小时前回复 合并两个字典/哈希,重复的项目,两个值相加 hash1.merge(hash2) {|dupkey,val1,val2| val1 + val2 } 楼主来个Python的写法? hhuai 49楼, 于16小时前回复 method_missing, 楼主来个?? gaicitadie 50楼, 于14小时前回复 #48楼 @FenRagwort ,这个暂时只想到了普通方法 for k,v in hash2.items():     if k in hash1:         hash1[k] += v     else:         hash1.setdefault(k,v) zw963 51楼, 于12小时前回复 #19楼 @skandhas 的确酷, 不说说实在的. group_by用来做这个, 真是有点大才小用了. reus 52楼, 于12小时前回复 #49楼 @hhuai class Foo:   def __getattr__(self, name):     def _foo(*arg, **kwargs):       return self.method_missing(name, *arg, **kwargs)     return _foo   def method_missing(self, name, *args, **kwargs):     print name, args, kwargs a = Foo() a.foo(‘bar’, baz = ‘baz’)   huacnlee 1楼, 于24小时前回复 irb> ‘11ab1111ab111ac11111’.scan(“ab”).count 2 ywencn 2楼, 于24小时前回复 1.9.2p290 :001 >  ‘11ab1111ab111ac11111’.count(‘ab’)  => 5 楼主想表达什么? ywencn 3楼, 于24小时前回复 哎呀。。。怎么python和ruby的count还不一样,哈哈 huacnlee 4楼, 于24小时前回复 Ruby 的 “”.count 统计的是后面所有的字符 dreamrise 5楼, 于24小时前回复 貌似_who还写过一个python与ruby转换的程序? gaicitadie 6楼, 于23小时前回复 奥运奖牌榜: 国家 金牌数 银牌数 铜牌数 china 37 26 11 usa 30 22 50 russia 30 33 20 中国习惯上先按金牌数排名,金牌数一样的按银牌数再排,如果银牌数再一样就按铜牌数排: >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) [(‘china’, 37, 26, 11), (‘russia’, 30, 33, 20), (‘usa’, 30, 22, 50)] 美国习惯上金牌银牌铜牌都是奖牌,所以按奖牌总数排序: >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:-(x[1]+x[2]+x[3])) [(‘usa’, 30, 22, 50), (‘russia’, 30, 33, 20), (‘china’, 37, 26, 11)] python的排序达到了类似SQL查询的能力,只需要告诉它排序的条件就可以了,python为数据而生 gaicitadie 7楼, 于23小时前回复 上面的例子是python模拟SQL的order by功能,下面的例子用python模拟SQL的where条件查询 统计金牌数超过35的国家: >>> [x for x in [(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)] if x[1]>35] [(‘china’, 37, 26, 11)] 统计奖牌总数超过100的国家: >>> [x for x in [(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)] if x[1]+x[2]+x[3]>100] [(‘usa’, 30, 22, 50)] huyong36 8楼, 于23小时前回复 @gaicitadie ruby是 [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] > 35} [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] + x[2] + x[3] > 100} quakewang 9楼, 于23小时前回复 #6楼 @gaicitadie order by 的ruby代码 [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|m| [-m[1], -m[2], -m[3]]} [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|m| -(m[1] + m[2] + m[3])]} skandhas 10楼, 于23小时前回复 从楼主的例子直接翻译到Ruby 1 中国习惯上先按金牌数排名,金牌数一样的按银牌数再排,如果银牌数再一样就按铜牌数排: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|x| [-x[1],-x[2],-x[3]]} 2 美国习惯上金牌银牌铜牌都是奖牌,所以按奖牌总数排序: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|x| -(x[1]+x[2]+x[3])} 3 统计金牌数超过35的国家: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] >35} 4 统计奖牌总数超过100的国家: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1]+x[2]+x[3] > 100} 这两个语言都挺类似,我觉得ruby的select更直观。 另 楼主这个帖子想表达什么?没看出什么来。如果说只通过sorted就说明python是为数据而生的话,那ruby不也是吗。哈哈 daqing 11楼, 于23小时前回复 我来写个Ruby版本的。 第一个,奖牌排序: data = [[:china, 27, 26, 11], [:usa, 20, 22, 50], [:russia, 30, 33, 20]] data.sort_by { |x| [-x[1], -x[2], -x[3]] } # 中国排序方法,按金/银/铜牌数 data.sort_by { |x| -(x[1] + x[2] + x[3]) } # 美国排序方法,按奖牌总数 第二个,奖牌统计: data.select { |x| x[1] > 35 } # 金牌数超过35的国家 data.select { |x| x[1] + x[2] + x[3] > 100 } # 奖牌总数超过100的国家 哪个更简洁,一目了然了吧。 daqing 12楼, 于23小时前回复 原来大家都在回复。。等我写出来才发现。 daqing 13楼, 于23小时前回复 #10楼 @skandhas 看了你的方法,才想到,select是更直接的做法。collect方法会包含nil值。 reus 14楼, 于23小时前回复 Why I Hate Advocacy http://www.perl.com/pub/2000/12/advocacy.html gaicitadie 15楼, 于23小时前回复 总统选举投票,初步唱票记录: [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] 根据唱票记录统计每人的票数并按从多到少排序 >>> l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] >>> sorted(set([(i, l.count(i)) for i in l]), key=lambda x:-x[1]) [(‘Jim’, 4), (‘bush’, 2), (‘obama’, 1)] clearJiang 16楼, 于23小时前回复 #15楼 @gaicitadie 不如直接用collections.Counter gaicitadie 17楼, 于23小时前回复 #16楼 @clearJiang 低版本没有collections daqing 18楼, 于23小时前回复 总统选举投票 l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.uniq.collect { |x| [x, l.count(x)] } => [[“Jim”, 4], [“bush”, 2], [“obama”, 1]] skandhas 19楼, 于22小时前回复 #15楼 @gaicitadie 根据唱票记录统计每人的票数并按从多到少排序: l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.group_by{|i| i}.map{|k,v| [k,v.length] } quakewang 20楼, 于22小时前回复 python要和ruby比 文件、字符操作或者数组、Hash操作的便利性绝对完败,要砸场还不如在性能上一棍子打死ruby。 bony 21楼, 于22小时前回复 这样的帖子应该多一点。长知识。@skandhas cool. quakewang 22楼, 于22小时前回复 #15楼 @gaicitadie [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’].inject(Hash.new(0)) {|h, e| h[e] += 1; h}.sort_by{|e| -e[1]} daqing 23楼, 于22小时前回复 说实话,Python的lambda匿名函数,跟Ruby的Block相比,从书写上就败了。 gaicitadie 24楼, 于22小时前回复 随机设置验证码的4个字符(不包括图片处理部分) >>> import random >>> s = ‘ABCDEFGHIJKLMNPRSTUVWXYZ’ >>> ”.join(random.sample((s),4)) ‘EXSG’ >>> ”.join(random.sample((s),4)) ‘TGYN’ >>> ”.join(random.sample((s),4)) ‘MEYP’ >>> ”.join(random.sample((s),4)) ‘TGIF’ >>> ”.join(random.sample((s),4)) ‘JDWF’ quakewang 25楼, 于22小时前回复 #24楼 @gaicitadie (‘A’..’Z’).to_a.sample(4).join reus 26楼, 于22小时前回复 #15楼 @gaicitadie 你这个算法是O(n ^ 2)的,应该用reduce def stat(acc, x):   acc.setdefault(x, 0)   acc[x] += 1   return acc sorted(reduce(stat,   [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’], {}).iteritems(),     key = lambda x: -x[1]) huyong36 27楼, 于22小时前回复 @skandhas cool,加上个排序。 gaicitadie 28楼, 于22小时前回复 #26楼 @reus reduce不如列表解析快,虽然list.count会重复统计 skandhas 29楼, 于22小时前回复 #27楼 @huyong36 对,是忘了排序 l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.group_by{|i| i}.map{|k,v| [k,v.length] }.sort_by{|name,count| -count } huacnlee 30楼, 于22小时前回复 风格不同而已,用起来都是一样方便。这个就是让我喜欢 Python 和 Ruby 的原因之一。 reus 31楼, 于22小时前回复 #28楼 @gaicitadie 就是慢在count调用上,for i in l遍历数组,且每个元素又再count遍历一次,O(n ^ 2) reduce只需要遍历一次,O(n) 不信可以测试下 huyong36 32楼, 于22小时前回复 这帖应该是捧场帖,我喜欢这样的学习。 raecoo 33楼, 于22小时前回复 受用 hysios 34楼, 于21小时前回复 •字符串查找 # python >>> ‘11ab1111ab111ac11111’.count(‘ab’) 2 # Ruby ruby-1.9.2-p290 >   ‘11ab111123ab111ac11111’.count ‘ab’, ‘b’ 2 hysios 35楼, 于21小时前回复 •奖牌排序 # python >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) [(‘china’, 37, 26, 11), (‘russia’, 30, 33, 20), (‘usa’, 30, 22, 50)] # ruby ruby-1.9.2-p290 > [[‘china’,37,26,11], [‘usa’,30,22,50],[‘russia’,30,33,20]].sort_by {|name,j,y,t| [-j,-y,-t] }  => [[“china”, 37, 26, 11], [“russia”, 30, 33, 20], [“usa”, 30, 22, 50]] hysios 36楼, 于21小时前回复 •奖牌统计 # python >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:-(x[1]+x[2]+x[3])) [(‘usa’, 30, 22, 50), (‘russia’, 30, 33, 20), (‘china’, 37, 26, 11)] # ruby ruby-1.9.2-p290 > [[‘china’,37,26,11], [‘usa’,30,22,50],[‘russia’,30,33,20]].sort_by {|name,j,y,t| [-j + -y + -t] }  => [[“usa”, 30, 22, 50], [“russia”, 30, 33, 20], [“china”, 37, 26, 11]] kfll 37楼, 于21小时前回复 捧场.. js: ‘11ab1111ab111ac11111’.match(/ab/g).length; ‘11ab1111ab111ac11111’.split(‘ab’).length - 1; 中式排名: [[37, 26, 11], [30, 22, 50], [30, 33, 20]].sort().reverse(); hysios 38楼, 于21小时前回复 总统选举投票,初步唱票记录: # ruby >>> l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] >>> sorted(set([(i, l.count(i)) for i in l]), key=lambda x:-x[1]) [(‘Jim’, 4), (‘bush’, 2), (‘obama’, 1)] # ruby ruby-1.9.2-p290 >  [‘bush’,’Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’].each_with_object({}) {|name,s| s[name] = s.fetch(name,0) + 1 }.sort  => [[“Jim”, 4], [“bush”, 3], [“obama”, 1]] huyong36 39楼, 于21小时前回复 #34楼 @hysios ‘11ab111123ab111ac11111’.count ‘ab’, ‘b’ 这样不对吧…这样只是找出来字符串里出现b的次数。 irb(main):106:0> ‘11ab111123ab111ac11111b’.count ‘ab’, ‘b’ => 3 hysios 40楼, 于21小时前回复 @huyong36 thx count是没办法实现的, 别的方法也不错 huyong36 41楼, 于20小时前回复 #40楼 @hysios 恩,请教 irb(main):115:0> ‘11ab111123ab111c11111’.count  ‘a’ => 2 irb(main):114:0> ‘11ab111123ab111c11111’.count  ‘ab’ => 4 字符可以统计,为什么字符串不能。。 jhjguxin 42楼, 于20小时前回复 @huyong36 count([other_str]+) → fixnum click to toggle source Each other_str parameter defines a set of characters to count. The intersection of these sets defines the characters to count in str. Any other_str that starts with a caret (^) is negated. The sequence c1–c2 means all characters between c1 and c2. Guest 43楼, 于20小时前回复 gaicitadie = [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].tap do |man|   def man.make_self(&process); process.call self; end   def man.become_egghead     `python -c “print( sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) )”`   end   def man.glow_up     dont_be_shy = true     unless self.respond_to? :more_elegant, dont_be_shy       def self.more_elegant         self.sort_by { |country, glods, silvers, bronzes| [-glods,-silvers,-bronzes] }       end     end     if self.respond_to? :become_egghead       class << self; remove_method :become_egghead; end     end     self   end end gaicitadie.make_self &:become_egghead gaicitadie.glow_up.make_self &:more_elegant geekontheway 44楼, 于20小时前回复 #41楼 @huyong36 ruby的count 统计的是字符的数量 所以’11ab111123ab111c11111’.count ‘ab’等同于’11ab111123ab111c11111’.count ‘a’ + ‘11ab111123ab111c11111’.count ‘b’ jhjguxin 45楼, 于20小时前回复 简而言之就是 取每一个字符的count的交集 huyong36 46楼, 于20小时前回复 @geekontheway @jhjguxin 3Q… hysios 47楼, 于20小时前回复 #41楼 @huyong36 count 是统计所有的字符,并不会把参数当成字符串处理 FenRagwort 48楼, 于17小时前回复 合并两个字典/哈希,重复的项目,两个值相加 hash1.merge(hash2) {|dupkey,val1,val2| val1 + val2 } 楼主来个Python的写法? hhuai 49楼, 于16小时前回复 method_missing, 楼主来个?? gaicitadie 50楼, 于14小时前回复 #48楼 @FenRagwort ,这个暂时只想到了普通方法 for k,v in hash2.items():     if k in hash1:         hash1[k] += v     else:         hash1.setdefault(k,v) zw963 51楼, 于12小时前回复 #19楼 @skandhas 的确酷, 不说说实在的. group_by用来做这个, 真是有点大才小用了. reus 52楼, 于12小时前回复 #49楼 @hhuai class Foo:   def __getattr__(self, name):     def _foo(*arg, **kwargs):       return self.method_missing(name, *arg, **kwargs)     return _foo   def method_missing(self, name, *args, **kwargs):     print name, args, kwargs a = Foo() a.foo(‘bar’, baz = ‘baz’)   huacnlee 1楼, 于24小时前回复 irb> ‘11ab1111ab111ac11111’.scan(“ab”).count 2 ywencn 2楼, 于24小时前回复 1.9.2p290 :001 >  ‘11ab1111ab111ac11111’.count(‘ab’)  => 5 楼主想表达什么? ywencn 3楼, 于24小时前回复 哎呀。。。怎么python和ruby的count还不一样,哈哈 huacnlee 4楼, 于24小时前回复 Ruby 的 “”.count 统计的是后面所有的字符 dreamrise 5楼, 于24小时前回复 貌似_who还写过一个python与ruby转换的程序? gaicitadie 6楼, 于23小时前回复 奥运奖牌榜: 国家 金牌数 银牌数 铜牌数 china 37 26 11 usa 30 22 50 russia 30 33 20 中国习惯上先按金牌数排名,金牌数一样的按银牌数再排,如果银牌数再一样就按铜牌数排: >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) [(‘china’, 37, 26, 11), (‘russia’, 30, 33, 20), (‘usa’, 30, 22, 50)] 美国习惯上金牌银牌铜牌都是奖牌,所以按奖牌总数排序: >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:-(x[1]+x[2]+x[3])) [(‘usa’, 30, 22, 50), (‘russia’, 30, 33, 20), (‘china’, 37, 26, 11)] python的排序达到了类似SQL查询的能力,只需要告诉它排序的条件就可以了,python为数据而生 gaicitadie 7楼, 于23小时前回复 上面的例子是python模拟SQL的order by功能,下面的例子用python模拟SQL的where条件查询 统计金牌数超过35的国家: >>> [x for x in [(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)] if x[1]>35] [(‘china’, 37, 26, 11)] 统计奖牌总数超过100的国家: >>> [x for x in [(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)] if x[1]+x[2]+x[3]>100] [(‘usa’, 30, 22, 50)] huyong36 8楼, 于23小时前回复 @gaicitadie ruby是 [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] > 35} [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] + x[2] + x[3] > 100} quakewang 9楼, 于23小时前回复 #6楼 @gaicitadie order by 的ruby代码 [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|m| [-m[1], -m[2], -m[3]]} [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|m| -(m[1] + m[2] + m[3])]} skandhas 10楼, 于23小时前回复 从楼主的例子直接翻译到Ruby 1 中国习惯上先按金牌数排名,金牌数一样的按银牌数再排,如果银牌数再一样就按铜牌数排: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|x| [-x[1],-x[2],-x[3]]} 2 美国习惯上金牌银牌铜牌都是奖牌,所以按奖牌总数排序: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|x| -(x[1]+x[2]+x[3])} 3 统计金牌数超过35的国家: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] >35} 4 统计奖牌总数超过100的国家: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1]+x[2]+x[3] > 100} 这两个语言都挺类似,我觉得ruby的select更直观。 另 楼主这个帖子想表达什么?没看出什么来。如果说只通过sorted就说明python是为数据而生的话,那ruby不也是吗。哈哈 daqing 11楼, 于23小时前回复 我来写个Ruby版本的。 第一个,奖牌排序: data = [[:china, 27, 26, 11], [:usa, 20, 22, 50], [:russia, 30, 33, 20]] data.sort_by { |x| [-x[1], -x[2], -x[3]] } # 中国排序方法,按金/银/铜牌数 data.sort_by { |x| -(x[1] + x[2] + x[3]) } # 美国排序方法,按奖牌总数 第二个,奖牌统计: data.select { |x| x[1] > 35 } # 金牌数超过35的国家 data.select { |x| x[1] + x[2] + x[3] > 100 } # 奖牌总数超过100的国家 哪个更简洁,一目了然了吧。 daqing 12楼, 于23小时前回复 原来大家都在回复。。等我写出来才发现。 daqing 13楼, 于23小时前回复 #10楼 @skandhas 看了你的方法,才想到,select是更直接的做法。collect方法会包含nil值。 reus 14楼, 于23小时前回复 Why I Hate Advocacy http://www.perl.com/pub/2000/12/advocacy.html gaicitadie 15楼, 于23小时前回复 总统选举投票,初步唱票记录: [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] 根据唱票记录统计每人的票数并按从多到少排序 >>> l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] >>> sorted(set([(i, l.count(i)) for i in l]), key=lambda x:-x[1]) [(‘Jim’, 4), (‘bush’, 2), (‘obama’, 1)] clearJiang 16楼, 于23小时前回复 #15楼 @gaicitadie 不如直接用collections.Counter gaicitadie 17楼, 于23小时前回复 #16楼 @clearJiang 低版本没有collections daqing 18楼, 于23小时前回复 总统选举投票 l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.uniq.collect { |x| [x, l.count(x)] } => [[“Jim”, 4], [“bush”, 2], [“obama”, 1]] skandhas 19楼, 于22小时前回复 #15楼 @gaicitadie 根据唱票记录统计每人的票数并按从多到少排序: l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.group_by{|i| i}.map{|k,v| [k,v.length] } quakewang 20楼, 于22小时前回复 python要和ruby比 文件、字符操作或者数组、Hash操作的便利性绝对完败,要砸场还不如在性能上一棍子打死ruby。 bony 21楼, 于22小时前回复 这样的帖子应该多一点。长知识。@skandhas cool. quakewang 22楼, 于22小时前回复 #15楼 @gaicitadie [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’].inject(Hash.new(0)) {|h, e| h[e] += 1; h}.sort_by{|e| -e[1]} daqing 23楼, 于22小时前回复 说实话,Python的lambda匿名函数,跟Ruby的Block相比,从书写上就败了。 gaicitadie 24楼, 于22小时前回复 随机设置验证码的4个字符(不包括图片处理部分) >>> import random >>> s = ‘ABCDEFGHIJKLMNPRSTUVWXYZ’ >>> ”.join(random.sample((s),4)) ‘EXSG’ >>> ”.join(random.sample((s),4)) ‘TGYN’ >>> ”.join(random.sample((s),4)) ‘MEYP’ >>> ”.join(random.sample((s),4)) ‘TGIF’ >>> ”.join(random.sample((s),4)) ‘JDWF’ quakewang 25楼, 于22小时前回复 #24楼 @gaicitadie (‘A’..’Z’).to_a.sample(4).join reus 26楼, 于22小时前回复 #15楼 @gaicitadie 你这个算法是O(n ^ 2)的,应该用reduce def stat(acc, x):   acc.setdefault(x, 0)   acc[x] += 1   return acc sorted(reduce(stat,   [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’], {}).iteritems(),     key = lambda x: -x[1]) huyong36 27楼, 于22小时前回复 @skandhas cool,加上个排序。 gaicitadie 28楼, 于22小时前回复 #26楼 @reus reduce不如列表解析快,虽然list.count会重复统计 skandhas 29楼, 于22小时前回复 #27楼 @huyong36 对,是忘了排序 l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.group_by{|i| i}.map{|k,v| [k,v.length] }.sort_by{|name,count| -count } huacnlee 30楼, 于22小时前回复 风格不同而已,用起来都是一样方便。这个就是让我喜欢 Python 和 Ruby 的原因之一。 reus 31楼, 于22小时前回复 #28楼 @gaicitadie 就是慢在count调用上,for i in l遍历数组,且每个元素又再count遍历一次,O(n ^ 2) reduce只需要遍历一次,O(n) 不信可以测试下 huyong36 32楼, 于22小时前回复 这帖应该是捧场帖,我喜欢这样的学习。 raecoo 33楼, 于22小时前回复 受用 hysios 34楼, 于21小时前回复 •字符串查找 # python >>> ‘11ab1111ab111ac11111’.count(‘ab’) 2 # Ruby ruby-1.9.2-p290 >   ‘11ab111123ab111ac11111’.count ‘ab’, ‘b’ 2 hysios 35楼, 于21小时前回复 •奖牌排序 # python >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) [(‘china’, 37, 26, 11), (‘russia’, 30, 33, 20), (‘usa’, 30, 22, 50)] # ruby ruby-1.9.2-p290 > [[‘china’,37,26,11], [‘usa’,30,22,50],[‘russia’,30,33,20]].sort_by {|name,j,y,t| [-j,-y,-t] }  => [[“china”, 37, 26, 11], [“russia”, 30, 33, 20], [“usa”, 30, 22, 50]] hysios 36楼, 于21小时前回复 •奖牌统计 # python >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:-(x[1]+x[2]+x[3])) [(‘usa’, 30, 22, 50), (‘russia’, 30, 33, 20), (‘china’, 37, 26, 11)] # ruby ruby-1.9.2-p290 > [[‘china’,37,26,11], [‘usa’,30,22,50],[‘russia’,30,33,20]].sort_by {|name,j,y,t| [-j + -y + -t] }  => [[“usa”, 30, 22, 50], [“russia”, 30, 33, 20], [“china”, 37, 26, 11]] kfll 37楼, 于21小时前回复 捧场.. js: ‘11ab1111ab111ac11111’.match(/ab/g).length; ‘11ab1111ab111ac11111’.split(‘ab’).length - 1; 中式排名: [[37, 26, 11], [30, 22, 50], [30, 33, 20]].sort().reverse(); hysios 38楼, 于21小时前回复 总统选举投票,初步唱票记录: # ruby >>> l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] >>> sorted(set([(i, l.count(i)) for i in l]), key=lambda x:-x[1]) [(‘Jim’, 4), (‘bush’, 2), (‘obama’, 1)] # ruby ruby-1.9.2-p290 >  [‘bush’,’Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’].each_with_object({}) {|name,s| s[name] = s.fetch(name,0) + 1 }.sort  => [[“Jim”, 4], [“bush”, 3], [“obama”, 1]] huyong36 39楼, 于21小时前回复 #34楼 @hysios ‘11ab111123ab111ac11111’.count ‘ab’, ‘b’ 这样不对吧…这样只是找出来字符串里出现b的次数。 irb(main):106:0> ‘11ab111123ab111ac11111b’.count ‘ab’, ‘b’ => 3 hysios 40楼, 于21小时前回复 @huyong36 thx count是没办法实现的, 别的方法也不错 huyong36 41楼, 于20小时前回复 #40楼 @hysios 恩,请教 irb(main):115:0> ‘11ab111123ab111c11111’.count  ‘a’ => 2 irb(main):114:0> ‘11ab111123ab111c11111’.count  ‘ab’ => 4 字符可以统计,为什么字符串不能。。 jhjguxin 42楼, 于20小时前回复 @huyong36 count([other_str]+) → fixnum click to toggle source Each other_str parameter defines a set of characters to count. The intersection of these sets defines the characters to count in str. Any other_str that starts with a caret (^) is negated. The sequence c1–c2 means all characters between c1 and c2. Guest 43楼, 于20小时前回复 gaicitadie = [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].tap do |man|   def man.make_self(&process); process.call self; end   def man.become_egghead     `python -c “print( sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) )”`   end   def man.glow_up     dont_be_shy = true     unless self.respond_to? :more_elegant, dont_be_shy       def self.more_elegant         self.sort_by { |country, glods, silvers, bronzes| [-glods,-silvers,-bronzes] }       end     end     if self.respond_to? :become_egghead       class << self; remove_method :become_egghead; end     end     self   end end gaicitadie.make_self &:become_egghead gaicitadie.glow_up.make_self &:more_elegant geekontheway 44楼, 于20小时前回复 #41楼 @huyong36 ruby的count 统计的是字符的数量 所以’11ab111123ab111c11111’.count ‘ab’等同于’11ab111123ab111c11111’.count ‘a’ + ‘11ab111123ab111c11111’.count ‘b’ jhjguxin 45楼, 于20小时前回复 简而言之就是 取每一个字符的count的交集 huyong36 46楼, 于20小时前回复 @geekontheway @jhjguxin 3Q… hysios 47楼, 于20小时前回复 #41楼 @huyong36 count 是统计所有的字符,并不会把参数当成字符串处理 FenRagwort 48楼, 于17小时前回复 合并两个字典/哈希,重复的项目,两个值相加 hash1.merge(hash2) {|dupkey,val1,val2| val1 + val2 } 楼主来个Python的写法? hhuai 49楼, 于16小时前回复 method_missing, 楼主来个?? gaicitadie 50楼, 于14小时前回复 #48楼 @FenRagwort ,这个暂时只想到了普通方法 for k,v in hash2.items():     if k in hash1:         hash1[k] += v     else:         hash1.setdefault(k,v) zw963 51楼, 于12小时前回复 #19楼 @skandhas 的确酷, 不说说实在的. group_by用来做这个, 真是有点大才小用了. reus 52楼, 于12小时前回复 #49楼 @hhuai class Foo:   def __getattr__(self, name):     def _foo(*arg, **kwargs):       return self.method_missing(name, *arg, **kwargs)     return _foo   def method_missing(self, name, *args, **kwargs):     print name, args, kwargs a = Foo() a.foo(‘bar’, baz = ‘baz’)   huacnlee 1楼, 于24小时前回复 irb> ‘11ab1111ab111ac11111’.scan(“ab”).count 2 ywencn 2楼, 于24小时前回复 1.9.2p290 :001 >  ‘11ab1111ab111ac11111’.count(‘ab’)  => 5 楼主想表达什么? ywencn 3楼, 于24小时前回复 哎呀。。。怎么python和ruby的count还不一样,哈哈 huacnlee 4楼, 于24小时前回复 Ruby 的 “”.count 统计的是后面所有的字符 dreamrise 5楼, 于24小时前回复 貌似_who还写过一个python与ruby转换的程序? gaicitadie 6楼, 于23小时前回复 奥运奖牌榜: 国家 金牌数 银牌数 铜牌数 china 37 26 11 usa 30 22 50 russia 30 33 20 中国习惯上先按金牌数排名,金牌数一样的按银牌数再排,如果银牌数再一样就按铜牌数排: >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) [(‘china’, 37, 26, 11), (‘russia’, 30, 33, 20), (‘usa’, 30, 22, 50)] 美国习惯上金牌银牌铜牌都是奖牌,所以按奖牌总数排序: >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:-(x[1]+x[2]+x[3])) [(‘usa’, 30, 22, 50), (‘russia’, 30, 33, 20), (‘china’, 37, 26, 11)] python的排序达到了类似SQL查询的能力,只需要告诉它排序的条件就可以了,python为数据而生 gaicitadie 7楼, 于23小时前回复 上面的例子是python模拟SQL的order by功能,下面的例子用python模拟SQL的where条件查询 统计金牌数超过35的国家: >>> [x for x in [(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)] if x[1]>35] [(‘china’, 37, 26, 11)] 统计奖牌总数超过100的国家: >>> [x for x in [(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)] if x[1]+x[2]+x[3]>100] [(‘usa’, 30, 22, 50)] huyong36 8楼, 于23小时前回复 @gaicitadie ruby是 [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] > 35} [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] + x[2] + x[3] > 100} quakewang 9楼, 于23小时前回复 #6楼 @gaicitadie order by 的ruby代码 [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|m| [-m[1], -m[2], -m[3]]} [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|m| -(m[1] + m[2] + m[3])]} skandhas 10楼, 于23小时前回复 从楼主的例子直接翻译到Ruby 1 中国习惯上先按金牌数排名,金牌数一样的按银牌数再排,如果银牌数再一样就按铜牌数排: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|x| [-x[1],-x[2],-x[3]]} 2 美国习惯上金牌银牌铜牌都是奖牌,所以按奖牌总数排序: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].sort_by{|x| -(x[1]+x[2]+x[3])} 3 统计金牌数超过35的国家: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1] >35} 4 统计奖牌总数超过100的国家: [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].select{|x| x[1]+x[2]+x[3] > 100} 这两个语言都挺类似,我觉得ruby的select更直观。 另 楼主这个帖子想表达什么?没看出什么来。如果说只通过sorted就说明python是为数据而生的话,那ruby不也是吗。哈哈 daqing 11楼, 于23小时前回复 我来写个Ruby版本的。 第一个,奖牌排序: data = [[:china, 27, 26, 11], [:usa, 20, 22, 50], [:russia, 30, 33, 20]] data.sort_by { |x| [-x[1], -x[2], -x[3]] } # 中国排序方法,按金/银/铜牌数 data.sort_by { |x| -(x[1] + x[2] + x[3]) } # 美国排序方法,按奖牌总数 第二个,奖牌统计: data.select { |x| x[1] > 35 } # 金牌数超过35的国家 data.select { |x| x[1] + x[2] + x[3] > 100 } # 奖牌总数超过100的国家 哪个更简洁,一目了然了吧。 daqing 12楼, 于23小时前回复 原来大家都在回复。。等我写出来才发现。 daqing 13楼, 于23小时前回复 #10楼 @skandhas 看了你的方法,才想到,select是更直接的做法。collect方法会包含nil值。 reus 14楼, 于23小时前回复 Why I Hate Advocacy http://www.perl.com/pub/2000/12/advocacy.html gaicitadie 15楼, 于23小时前回复 总统选举投票,初步唱票记录: [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] 根据唱票记录统计每人的票数并按从多到少排序 >>> l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] >>> sorted(set([(i, l.count(i)) for i in l]), key=lambda x:-x[1]) [(‘Jim’, 4), (‘bush’, 2), (‘obama’, 1)] clearJiang 16楼, 于23小时前回复 #15楼 @gaicitadie 不如直接用collections.Counter gaicitadie 17楼, 于23小时前回复 #16楼 @clearJiang 低版本没有collections daqing 18楼, 于23小时前回复 总统选举投票 l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.uniq.collect { |x| [x, l.count(x)] } => [[“Jim”, 4], [“bush”, 2], [“obama”, 1]] skandhas 19楼, 于22小时前回复 #15楼 @gaicitadie 根据唱票记录统计每人的票数并按从多到少排序: l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.group_by{|i| i}.map{|k,v| [k,v.length] } quakewang 20楼, 于22小时前回复 python要和ruby比 文件、字符操作或者数组、Hash操作的便利性绝对完败,要砸场还不如在性能上一棍子打死ruby。 bony 21楼, 于22小时前回复 这样的帖子应该多一点。长知识。@skandhas cool. quakewang 22楼, 于22小时前回复 #15楼 @gaicitadie [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’].inject(Hash.new(0)) {|h, e| h[e] += 1; h}.sort_by{|e| -e[1]} daqing 23楼, 于22小时前回复 说实话,Python的lambda匿名函数,跟Ruby的Block相比,从书写上就败了。 gaicitadie 24楼, 于22小时前回复 随机设置验证码的4个字符(不包括图片处理部分) >>> import random >>> s = ‘ABCDEFGHIJKLMNPRSTUVWXYZ’ >>> ”.join(random.sample((s),4)) ‘EXSG’ >>> ”.join(random.sample((s),4)) ‘TGYN’ >>> ”.join(random.sample((s),4)) ‘MEYP’ >>> ”.join(random.sample((s),4)) ‘TGIF’ >>> ”.join(random.sample((s),4)) ‘JDWF’ quakewang 25楼, 于22小时前回复 #24楼 @gaicitadie (‘A’..’Z’).to_a.sample(4).join reus 26楼, 于22小时前回复 #15楼 @gaicitadie 你这个算法是O(n ^ 2)的,应该用reduce def stat(acc, x):   acc.setdefault(x, 0)   acc[x] += 1   return acc sorted(reduce(stat,   [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’], {}).iteritems(),     key = lambda x: -x[1]) huyong36 27楼, 于22小时前回复 @skandhas cool,加上个排序。 gaicitadie 28楼, 于22小时前回复 #26楼 @reus reduce不如列表解析快,虽然list.count会重复统计 skandhas 29楼, 于22小时前回复 #27楼 @huyong36 对,是忘了排序 l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] l.group_by{|i| i}.map{|k,v| [k,v.length] }.sort_by{|name,count| -count } huacnlee 30楼, 于22小时前回复 风格不同而已,用起来都是一样方便。这个就是让我喜欢 Python 和 Ruby 的原因之一。 reus 31楼, 于22小时前回复 #28楼 @gaicitadie 就是慢在count调用上,for i in l遍历数组,且每个元素又再count遍历一次,O(n ^ 2) reduce只需要遍历一次,O(n) 不信可以测试下 huyong36 32楼, 于22小时前回复 这帖应该是捧场帖,我喜欢这样的学习。 raecoo 33楼, 于22小时前回复 受用 hysios 34楼, 于21小时前回复 •字符串查找 # python >>> ‘11ab1111ab111ac11111’.count(‘ab’) 2 # Ruby ruby-1.9.2-p290 >   ‘11ab111123ab111ac11111’.count ‘ab’, ‘b’ 2 hysios 35楼, 于21小时前回复 •奖牌排序 # python >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) [(‘china’, 37, 26, 11), (‘russia’, 30, 33, 20), (‘usa’, 30, 22, 50)] # ruby ruby-1.9.2-p290 > [[‘china’,37,26,11], [‘usa’,30,22,50],[‘russia’,30,33,20]].sort_by {|name,j,y,t| [-j,-y,-t] }  => [[“china”, 37, 26, 11], [“russia”, 30, 33, 20], [“usa”, 30, 22, 50]] hysios 36楼, 于21小时前回复 •奖牌统计 # python >>> sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:-(x[1]+x[2]+x[3])) [(‘usa’, 30, 22, 50), (‘russia’, 30, 33, 20), (‘china’, 37, 26, 11)] # ruby ruby-1.9.2-p290 > [[‘china’,37,26,11], [‘usa’,30,22,50],[‘russia’,30,33,20]].sort_by {|name,j,y,t| [-j + -y + -t] }  => [[“usa”, 30, 22, 50], [“russia”, 30, 33, 20], [“china”, 37, 26, 11]] kfll 37楼, 于21小时前回复 捧场.. js: ‘11ab1111ab111ac11111’.match(/ab/g).length; ‘11ab1111ab111ac11111’.split(‘ab’).length - 1; 中式排名: [[37, 26, 11], [30, 22, 50], [30, 33, 20]].sort().reverse(); hysios 38楼, 于21小时前回复 总统选举投票,初步唱票记录: # ruby >>> l = [‘Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’] >>> sorted(set([(i, l.count(i)) for i in l]), key=lambda x:-x[1]) [(‘Jim’, 4), (‘bush’, 2), (‘obama’, 1)] # ruby ruby-1.9.2-p290 >  [‘bush’,’Jim’, ‘bush’, ‘Jim’, ‘Jim’, ‘Jim’, ‘bush’, ‘obama’].each_with_object({}) {|name,s| s[name] = s.fetch(name,0) + 1 }.sort  => [[“Jim”, 4], [“bush”, 3], [“obama”, 1]] huyong36 39楼, 于21小时前回复 #34楼 @hysios ‘11ab111123ab111ac11111’.count ‘ab’, ‘b’ 这样不对吧…这样只是找出来字符串里出现b的次数。 irb(main):106:0> ‘11ab111123ab111ac11111b’.count ‘ab’, ‘b’ => 3 hysios 40楼, 于21小时前回复 @huyong36 thx count是没办法实现的, 别的方法也不错 huyong36 41楼, 于20小时前回复 #40楼 @hysios 恩,请教 irb(main):115:0> ‘11ab111123ab111c11111’.count  ‘a’ => 2 irb(main):114:0> ‘11ab111123ab111c11111’.count  ‘ab’ => 4 字符可以统计,为什么字符串不能。。 jhjguxin 42楼, 于20小时前回复 @huyong36 count([other_str]+) → fixnum click to toggle source Each other_str parameter defines a set of characters to count. The intersection of these sets defines the characters to count in str. Any other_str that starts with a caret (^) is negated. The sequence c1–c2 means all characters between c1 and c2. Guest 43楼, 于20小时前回复 gaicitadie = [[‘china’,37,26,11], [‘usa’,30,22,50], [‘russia’,30,33,20]].tap do |man|   def man.make_self(&process); process.call self; end   def man.become_egghead     `python -c “print( sorted([(‘china’,37,26,11), (‘usa’,30,22,50), (‘russia’,30,33,20)], key=lambda x:(-x[1],-x[2],-x[3])) )”`   end   def man.glow_up     dont_be_shy = true     unless self.respond_to? :more_elegant, dont_be_shy       def self.more_elegant         self.sort_by { |country, glods, silvers, bronzes| [-glods,-silvers,-bronzes] }       end     end     if self.respond_to? :become_egghead       class << self; remove_method :become_egghead; end     end     self   end end gaicitadie.make_self &:become_egghead gaicitadie.glow_up.make_self &:more_elegant geekontheway 44楼, 于20小时前回复 #41楼 @huyong36 ruby的count 统计的是字符的数量 所以’11ab111123ab111c11111’.count ‘ab’等同于’11ab111123ab111c11111’.count ‘a’ + ‘11ab111123ab111c11111’.count ‘b’ jhjguxin 45楼, 于20小时前回复 简而言之就是 取每一个字符的count的交集 huyong36 46楼, 于20小时前回复 @geekontheway @jhjguxin 3Q… hysios 47楼, 于20小时前回复 #41楼 @huyong36 count 是统计所有的字符,并不会把参数当成字符串处理 FenRagwort 48楼, 于17小时前回复 合并两个字典/哈希,重复的项目,两个值相加 hash1.merge(hash2) {|dupkey,val1,val2| val1 + val2 } 楼主来个Python的写法? hhuai 49楼, 于16小时前回复 method_missing, 楼主来个?? gaicitadie 50楼, 于14小时前回复 #48楼 @FenRagwort ,这个暂时只想到了普通方法 for k,v in hash2.items():     if k in hash1:         hash1[k] += v     else:         hash1.setdefault(k,v) zw963 51楼, 于12小时前回复 #19楼 @skandhas 的确酷, 不说说实在的. group_by用来做这个, 真是有点大才小用了. reus 52楼, 于12小时前回复 #49楼 @hhuai class Foo:   def __getattr__(self, name):     def _foo(*arg, **kwargs):       return self.method_missing(name, *arg, **kwargs)     return _foo   def method_missing(self, name, *args, **kwargs):     print name, args, kwargs a = Foo() a.foo(‘bar’, baz = ‘baz’)   Francis.J(864248765)  13:19:50 >>> l=[‘a’,’a’,’b’,’b’,’b’,’c’,’c’] >>> sorted(set([i for i in l])) [‘a’, ‘b’, ‘c’] Francis.J(864248765)  13:20:01 这样还行 Francis.J(864248765)  13:21:15 但是感觉 没有 pop 省资源 GG(75865965)  13:26:39 cat cat source.txt |uniq

Helper Antipatterns

Helper Antipatterns

Helper AntiPatterns

Helper (輔助方法)的存在目的是用來輔助整理 View 中內嵌的複雜 Ruby 程式碼。設計得當的 Helper 可以加速專案的開發,以及增進程式的可讀性。然而,設計不好的 Helper 卻可能造成嚴重的反效果。

以下列舉常見的幾種糟糕的 Helper 設計模式:

1. 矯往過正:用 Helper 作 partial 該做的事

開發者以為 partial 效率是低下的,刻意不使用 partial,而改用 Helper 完成所有的動作:將需要重複使用的 HTML 通通寫成了 Ruby code,串接成 HTML: def show_index_block(block_name, post, is_show_game)

block_title = content_tag(:h3, block_name) section_header = content_tag(:div, block_title, :class => “section-header”)

game_name = is_show_game ? “【 #{post.games.first.name} 】” : “” title = content_tag(:h4, link_to(“#{game_name} #{post.title}”, post_path(post))) image = content_tag(:div, render_post_image(post), :class => “thumbnail”) content = content_tag(:p, truncate( post.content, :length => 100)) section_content = content_tag(:div, “#{title}#{image}#{content}”, :class => “section-content”)

section_footer = content_tag(:div, link_to(“閱讀全文”, post_path(post)), :class => “section-footer”)

return content_tag(:div, “#{section_header}#{section_content}#{section_footer}” , :class => “article-teaser”) end  Helper 的作用只是協助整理 HTML 中的邏輯程式碼。若有大片 HTML 需要重複使用,應當需要利用 partial 機制進行 HTML 的重複利用。這樣的寫法,非但效率低下(可以用 HTML 產生,卻使用 Ruby 呼叫 Tag Helper,且製造大量 Ruby Object),且嚴重降低程式的可讀性,其他維護者將難以對這樣的 DOM 進行後續的維護翻修。

 

  2. 容易混淆:在 Helper 裡面穿插 HTML tag

這也是另外一個矯枉過正的例子,不過方向剛好相反:「因為覺得使用 Ruby code 產生 HTML tag 可能浪費效能,而直接插入 HTML 在 Helper 裡面與 Ruby Code 混雜。」也造成了專案維護上的困難:因為 Ruby 中的字串是使用雙引號”,而 HTML 也是使用雙引號”,,所以就必須特別加入 \“ 跳脫,否則就可能造成 syntax error。 錯誤 def post_tags_tag(post, opts = {})

….

raw tags.collect { |tag| “#{tag}” }.join(“, ”) end 大量的 “ 混雜在程式碼裡面,嚴重造成程式的可閱讀性,而且發生 syntax error 時難以 debug。 def post_tags_tag(post, opts = {})

….

raw tags.collect { |tag| “#{tag}” }.join(“, ”) end 即便換成 ‘ 單引號,狀況並沒有好上多少。 def post_tags_tag(post, opts = {})

raw tags.collect { |tag| link_to(tag,posts_path(:tag => tag)) }.join(“, ”) end 正確的作法應該是妥善使用 Rails 內建的 Helper,使 Helper 裡面維持著都是 Ruby code 的狀態,並且具有高可讀性。

  1. 強耦合:把 CSS 應該做的事綁在 Ruby Helper 上。

錯誤 def red_alert(message) return content_tag(:span,message, :style => “font-color: red;”) end

def green_notice(message) return content_tag(:span,message, :style => “font-color: green;”) end 開發者不熟悉 unobtrusive 的設計手法,直接就把 design 就綁上了 Ruby Helper。將來設計上若需要變更時,難以修改或擴充。 正確 def stickies(message, message_type) content_tag(:span,message, :class => message_type.to_sym) end

Please Login!! 樣式應該由 CSS 決定,使用 CSS class 控制,而非強行綁在 Helper 上。 4. 重複發明輪子

Rails 已內建許多實用 Helper,開發者卻以較糟的方式重造輪子。在此舉幾個比較經典的案例:

cycle 如何設計 table 的雙色列效果?

<% count = 0 > <table> <% @items.each do |item| %> <% if count % 2 == 0 %> <% css_class = “even ”%> <% else %> <% css_class = “odd” %> <% end %> <tr class=“<%= css_class %>”> <td>item</td> </tr> <% count += 1%> <% end %> </table>

一般的想法會是使用兩種不同 CSS class : even 與 odd,著上不同的顏色。

<table> <% @items.each_with_index do |item, count| %> <% if count % 2 == 0 %> <% css_class = “even ”%> <% else %> <% css_class = “odd” %> <% end %> <tr class=“<%= css_class %>”> <td>item</td> </tr> <% count += 1%> <% end %> </table>

這是一般粗心者會犯的錯誤。實際上 Ruby 的 Array 內建 each_with_index,不需另外宣告一個 count。 優

<table> <% @items.each_with_index do |item, count| %> <% if count % 2 == 0 %> <% css_class = “even ”%> <% else %> <% css_class = “odd” %> <% end %> <tr class=“<%= css_class %>”> <td>item</td> </tr> <% count += 1%> <% end %> </table>

但其實還有更簡便的方法:Rails 內建了 cycle 這個 Helper。所以只要這樣寫就好了…

<table> <% @items.each do |item| %> <trodd”, “even”) %>“> <td>item</td> </tr> <% end %> </table>

常用你可能不知道的 Helper

限於篇幅,直接介紹幾個因為使用機率高,所以很容易被重造輪子的 Helper。開發者會寫出的相關 AntiPattern 部分就跳過了。

#TODO: examples

5. Tell, dont ask

這也是在 View 中會常出現的問題,直接違反了 Law of Demeter 原則,而造成了效能問題。十之八九某個 View 緩慢無比,最後抓出來背後幾乎都是這樣的原因。

不少開發者會設計出這樣的 helper:

  def post_tags_tag(post, opts = {}) tags = post.tags tags.collect { |tag| link_to(tag,posts_path(:tag => tag)) }.join(“, ”) end

這種寫法會造成在 View 中,執行迴圈時,造成不必要的大量 query (n+1),以及在 View 中製造不確定數量的大量物件。View 不僅效率低落也無法被 optimized。 def post_tags_tag(post, tags, opts = {}) tags.collect { |tag| link_to(tag,posts_path(:tag => tag)) }.join(“, ”) end

def index @posts = Post.recent.includes(:tags) end 正確的方法是使用 Tell, dont ask 原則,主動告知會使用的物件,而非讓 Helper 去猜。並配合 ActiveRecord 的 includes 減少不必要的 query( includes 可以製造 join query ,一次把需要的 posts 和 tags 撈出來)。

且在 controller query 有 object cache 效果,在 view 中則無。

小結

Helper 是 Rails Developer 時常在接觸的工具。但可惜的是,多數開發者卻無法將此利器使得稱手,反而造成了更多問題。在我所曾經參與的幾十個 Rails 專案中,很多設計和效能問題幾乎都是因為寫的不好的 View / Helper 中的 slow query 或伴隨產生的大量 object 所造成的 memory bloat 導致的。但參與專案的開發者並沒有那麼多的經驗,能夠抓出確切的病因,卻都將矛頭直接是 Rails 的效能問題,或者是沒打上 Cache 的關係。這樣的說法只是把問題掩蓋起來治標,而非治本。

下次若有遇到 performance issue,請先往 View 中瞧看看是不是裡面出現了問題。也許你很快就可以找到解答。

Posted by xdite Jan 12th, 2012

Factory_girl Validation Failed

factory_girl Validation failed

You need to use a sequence to prevent the creation of user objects with the same email, since you must have a validation for the uniqueness of emails in your User model.

Factory.sequence :email do |n|
  “test#{n}@example.com”
end

Factory.define :user do |user|
  user.name "Testing User"
  user.email { Factory.next(:email) }
  user.password "foobar"
  user.password_confirmation "foobar"
end

You can read more in the Factory Girl documentation.

 

Using factory_girl to create several instances of a class that belongs to another class causes a Validation failed error.

This happens because each instance tries to automatically create the object to which it belongs. However since the first instance creates it, the following instances crash because the object already exists.

I have been struggling to solve this problem and it seems finally I arrived to a satisfactory solution.

The problem arises when there is a one to many relationship between to classes and I try to create several instances of a class.

In my case I have a subdomain which has many users:

class Subdomain < ActiveRecord::Base validates_uniqueness_of :name, :case_sensitive => false has_many :users end class User < ActiveRecord::Base belongs_to: subdomain endThe factories are as follows: FactoryGirl.define do factory :subdomain do name ‘test-subdomain’ end factory :user do subdomain email ‘test-user@example.com’ password ‘123456¿ password_confirmation ‘123456’ end endWhen I try to create two users using the following code: FactoryGirl.create(:user, :email => “first@example.com”) FactoryGirl.create(:user, :email => “second@example.com”)I get the following error:

Validation failed: Name has already been taken (ActiveRecord::RecordInvalid)This happens because when the first user is created, the default subdomain is created too and when the second user is created the subdomain already exists.

If we want to use the same subdomain for both users we can do the following:

s = FactoryGirl.create(:subdomain) FactoryGirl.create(:user, :email => “first@example.com”, :subdomain => s) FactoryGirl.create(:user, :email => “second@example.com”, :subdomain => s)However as our has_many relations become deeper, test data creation becomes more complex.

My solution to this problem is as to change the factory for user:

factory :user do subdomain { Subdomain.find_by_name(‘test-subdomain’) || FactoryGirl.create(:subdomain) } email ‘test-user@example.com’ password ‘123456’ password_confirmation ‘123456’ endIn this case the factory uses the default subdomain if it already exists, avoiding the validation problem.

Now if we use the initial code, it works without problem, assigning both users to the default subdomain

FactoryGirl.create(:user, :email => “first@example.com”) FactoryGirl.create(:user, :email => “second@example.com”)However this does not limit us to use the same subdomain for all users:

other_subdomain = FactoryGirl.create(:subdomain, :name => “other-subdomain”)

FactoryGirl.create(:user, :email => “first@example.com”, :subdomain => other_subdomain) FactoryGirl.create(:user, :email => “second@example.com”)In this case, the first user is assigned to other-subdomain and the second user is assigned to test-subdomain.