← Ruby EnglishChapter 13 of 13

Best Practices

## Learning Objectives - Write idiomatic Ruby code - Follow Ruby style guide conventions - Use Ruby's strengths effectively - Avoid common pitfalls ## Ruby Style Guide ### Naming Conventions ```ruby # Variables and methods: snake_case first_name = "Alice" def calculate_total end # Classes and modules: CamelCase class UserAccount end module PaymentProcessing end # Constants: SCREAMING_SNAKE_CASE MAX_RETRY_COUNT = 3 DEFAULT_TIMEOUT = 30 # Booleans: end with ? or is_ valid? = true is_active = true has_children = false # Bang methods: end with ! def sort! @data.sort! end # Dangerous methods: end with ! def delete! @record.destroy end ``` ### Indentation and Spacing ```ruby # Use 2 spaces for indentation (not tabs) def method_name if condition do_something end end # Spaces around operators total = a + b result = x * y / z # No spaces around parentheses in method definitions def greet(name) "Hello, #{name}" end # Space after commas array = [1, 2, 3] hash = { name: "Alice", age: 30 } ``` ### Line Length ```ruby # Keep lines under 120 characters # If needed, break with continuation long_method_name(arg1, arg2, arg3, arg4, arg5) # Or use backslash long_result = some_method(arg1) + another_method(arg2) ``` ## Idiomatic Ruby ### Prefer Enumerable Methods ```ruby # Bad for n in numbers puts n end # Good numbers.each { |n| puts n } # Bad result = [] numbers.each { |n| result << n * 2 } # Good result = numbers.map { |n| n * 2 } # Bad found = nil items.each { |item| found = item if item.valid? } # Good found = items.find { |item| item.valid? } ``` ### Use Symbol to Proc ```ruby # Long form names.map { |name| name.upcase } # Idiomatic names.map(&:upcase) ``` ### Use ||= Sparingly ```ruby # Can cause issues with falsy values value = false value ||= default # value becomes default (wrong!) # Safer approach value = default if value.nil? # Or value = value ? value : default # Or (Ruby 2.3+) value &&= value # Only assigns if truthy ``` ### Parallel Assignment ```ruby # Swap variables idiomatically a, b = b, a # Avoid when not necessary x = 1 y = 2 z = x + y # Just use separate lines ``` ### Conditionals ```ruby # Ternary for simple cases status = passed ? "Success" : "Failed" # Guard clauses def process(data) return unless data.valid? # ... main logic end # Single-line modifier puts "debug" if debug_mode ``` ## Common Pitfalls ### Mutating Constants ```ruby # Bad DATA = [1, 2, 3] DATA << 4 # Modifies the original! # Good - freeze DATA = [1, 2, 3].freeze # Or clone when needed modified = DATA.dup << 4 ``` ### Shadowing Variables ```ruby # This shadows outer 'name' accidentally name = "Alice" [1, 2, 3].each do |name| # Now name is 1, 2, 3 in each iteration end puts name # "Alice" - still works but confusing ``` ### Premature Optimization ```ruby # Don't sacrifice readability for micro-optimizations # Bad result = map { |e| e.to_s } * "" # Good and clear result = map(&:to_s).join ``` ### Monkey Patching ```ruby # Risky - can break other code class String def is_email? self =~ /@/ end end # Safer - use refinements module StringExtensions refine String do def is_email? self =~ /@/ end end end ``` ## Code Organization ### Single Responsibility ```ruby # Bad - does multiple things def process_user(user) validate(user) save(user) send_email(user) log(user) end # Good - separate concerns def register_user(user) validate(user) save(user) end def welcome_user(user) send_email(user) log(user) end ``` ### Method Length ```ruby # Bad - method does too much def import_and_process_data # 100 lines of code end # Good - break into smaller methods def import_data end def process_data end ``` ### Class Length ```ruby # Bad - God object class UserManager def create_user end def delete_user end def authenticate end def send_email end def generate_report end # 50 more methods... end # Good - Single Responsibility class UserService def create_user end end class AuthenticationService def authenticate end end class NotificationService def send_email end end ``` ## Documentation ### Comments ```ruby # Good comment - explains WHY, not WHAT # Retry logic needed because external API can be flaky MAX_RETRIES = 3 # Bad comment - redundant # Increment counter by 1 counter += 1 ``` ### RDoc/YARD ```ruby # @param name [String] the user's name # @param age [Integer] the user's age # @return [User] the created user # @raise [ArgumentError] if name is blank def create_user(name:, age:) end ``` ## Performance Tips ### Lazy Evaluation ```ruby # Bad - creates full array data = (1..1_000_000).map { |n| heavy_computation(n) }.select { |n| n.valid? } # Good - processes one at a time data = (1..1_000_000).lazy.map { |n| heavy_computation(n) }.select { |n| n.valid? }.first(100) ``` ### String Building ```ruby # Bad - creates intermediate strings html = "" parts.each { |part| html += part } # Good html = parts.join ``` ### Use bang methods when appropriate ```ruby # When you don't need the original list.sort! # More efficient than list = list.sort ``` ## Testing Best Practices ```ruby # Test behavior, not implementation # Bad expect(user.instance_variable_get(:@name)).to eq("Alice") # Good expect(user.name).to eq("Alice") # Use descriptive test names # Bad it "test1" do end # Good it "returns the user's full name when first and last are present" do end # One assertion per test (generally) # But compound assertions are okay expect(user).to have_attributes(name: "Alice", age: 30, valid: true) ``` ## Security Best Practices ### Input Validation ```ruby # Always validate input def process_order(order_id) raise ArgumentError, "Invalid order ID" unless order_id.is_a?(Integer) end # Sanitize before SQL (use parameterized queries) # User.where("name = ?", params[:name]) # With ActiveRecord ``` ### Avoid eval ```ruby # Dangerous - never use with user input eval(user_input) # Safer alternatives # For code: parse with Ripper # For data: use JSON/YAML ``` ## Summary - Follow Ruby naming conventions: snake_case, CamelCase, SCREAMING_SNAKE_CASE - Use Enumerable methods over manual loops - Prefer `map`, `select`, `find` over `each` with conditionals - Use guard clauses for early returns - Keep methods short and focused - Write tests for behavior, not implementation - Use bang methods when mutation is intended - Avoid monkey patching without refinements - Document WHY, not WHAT - Keep lines under 120 characters

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →