0% found this document useful (0 votes)
17 views25 pages

2 - Idiomatic Code

Uploaded by

conapoh603
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views25 pages

2 - Idiomatic Code

Uploaded by

conapoh603
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 25

Service Oriented Architecture

Week 2
Idiomatic Code

Programming Tools Functional Iteration Good Code


RVM/REPL/Editors/Rubocop Blocks, Map/Reduce, Style Guide Tested, Idiomatic, Readable
REPL: irb vs. Pry Functional iteration with blocks Improve Readability
arr = [2,3,1,6,4,9] def formal_name(full_name) Good
Read Execute Print Loop names = full_name.split Code works (passes tests)
middle = names[1..names.length - 2]
arr.each { |n| puts n } Bad
$ irb $ pry arr.select { |n| n.odd? } Process is not clear
if middle.count > 0
2.1.2 :001 > [1] pry(main)> def greet(name) middle_names = middle.map { |mid| mid[0] + '.' }.join(' ') (i.e., the business problem is not clear)
[1] pry(main)* puts "Hello #{name}!" "#{names.first} #{middle_names} #{names.last}"
2.1.2 :002 > [1] pry(main)* end 2 3 1 6 4 9 2 3 1 6 4 9 3 1 9 else We must refactor to improve readability
hello => :greet full_name - refactor: change the structure of code
=> nil [2] pry(main)> greet('Soumya') end without changing its behavior
Hello Soumya! number 2
SELECT end - Ensure tests pass while refactoring
2.1.2 :d003 > Time.now => nil number 3
=> 2014-09-21 16:33:45 +0800 [3] pry(main)> edit greet EACH number
number
1
6 Good
number 4
Code works (passes tests) def formal_name(full_name)
number 9
2.1.2 :004 > def greet(name) def greet(name) Less code names = full_name.split
2.1.2 :005?> puts "Hello #{name}!" puts "Hello #{name}! How are you?" Process is clear middle = names[1..-2]
2.1.2 :006?> end end middle_names = middle.map { |name| name[0] + '.' }.join(' ') unless middle.empty?
=> :greet [names.first, middle_names, names.last].compact.join(' ')
Bad
2.1.2 :007 > [4] pry(main)> greet('Soumya') end
One line too long (style)
2.1.2 :008 > greet('Soumya') Hello Soumya! How are you? arr.map { |n| n*2 } arr.reduce { |n1, n2| "#{n1}-#{n2}" } Complex line hides purpose
Hello Soumya! => nil
=> nil [5] pry(main)>
2.1.2 :009 > 2 3 1 6 4 9 4 6 2 12 8 18 2 3 1 6 4 9 "2-3-1-6-4-9" Extract complex logic into new method
“Rubyists, It’s Time to PRY Yourself Off IRB!” # abbreviates words: takes array of names, returns array of abbreviated words - method name describes purpose
http://www.sitepoint.com/rubyists-time-pry-irb/ def abbreviated(words)
- how to change your editor for Pry:" words.map { |word| word[0] + '.' }.join(' ') unless words.empty? Comments describe method API
MAP REDUCE end
Just for fun: The world’s simplest REPL! "
Create a file: ~/.pryrc" Original method’s purpose is clear:
# formalizes full name: takes string, returns string
loop { print "myrepl> "; puts eval gets } Add line: purpose and solution are the same!
def formal_name(full_name)
Pry.config.editor = ’n'
names = full_name.split
Java 8: No more loops: http://www.deadcoderising.com/java-8-no-more-loops/ Use variable names to describe code
middle_initials = abbreviated(names[1..-2])
Performing Map, Filter and Reduce in D: https://mmstickman.wordpress.com/2015/01/29/performing-map-filter-and-reduce-in-d/ (e.g., middle_names)
[names.first, middle_initials, names.last].flatten.compact.join(' ')
Swift Guide to Map Filter Reduce: http://useyourloaf.com/blog/swift-guide-to-map-filter-reduce/
https://github.com/ISS-SOA/class-SOA-repo/wiki/resource-general#repl-pry Mapping Iterations in Rust: https://doc.rust-lang.org/std/iter/struct.Map.html end

1
Rising Competition

Competitor can update faster


(why is that?!?)
Features

Competitor seems to be more innovative


(why is that?!?)

you competitor

Time

2
Rewrites are Common!

v2.0

Features
Lost time and
opportunities
(and new risks?)
v1.0

Joel Spolsky
Things You Should Never Do Give up and start
complete rewrite
you competitor

Time
https://www.wired.com/2013/06/facebook-hhvm-saga/

Pobar oversees a team of engineers charged with rebuilding the very foundation of the world's most
popular social network. They've toiled on this project for more than three years now…

3
Unix Shells
Command line interface for Unix operations

Shell scripting language commands


ls -al list all files in current folder (+ directories and special files; long format)

pwd return the current working directory

cat <filename> print file with name <filename> to screen

Dotfiles for different shells

bash zsh zsh is a slightly more modern and easy to use shell
zsh is default in newer versions of MacOS
(bourne again shell) (Z shell)
.profile .profile setup commands for any shell or windowing system

.bashrc .zshrc commands & scripts to be run to configure any new shell
window that is opened
.bash_profile .zshenv commands & scripts to be run on user login into a shell
with username/password

4
Command Line Text Editors
Nano Emacs / Vi
Basic features
Best if you are new to Unix

http://www.tuxradar.com/content/text-editing-nano-made-easy http://www.tuxradar.com/content/emacs-tutorial-beginners
http://www.masteringemacs.org/articles/2010/11/01/running-shells-in-emacs-overview

Set default editor for Unix processes:


Add or change the line in the .bashrc or ~/.bashrc or ~/.zshrc
.zshrc file in your home folder (cd ~) export EDITOR=nano

Any unix process (e.g., git) that wants you to edit text or a file will now
automatically invoke your preferred text editor.

5
https://github.com/ISS-SOA/class-SOA-repo/wiki/resource-general#editors
GUI Text Editors

VisualStudio Code
code.visualstudio.com

Advantages
- Multiple cursors
- Extensive plugin options: git support, etc.
- Command Palette: ctrl-shift-p (Mac: cmd-shift-p)

6
rbenv
https://github.com/rbenv/rbenv

Use rbenv to manage your Ruby version Run a ruby app from the command line
$ rbenv $ ruby myapp.rb
# lists all rbenv commands

$ rbenv install --list


# lists all installable ruby versions Ruby has many variations
$ rbenv install 3.1.2 cruby (MRI) original and most popular variant

$ rbenv install truffleruby-20.0.0


jruby compatible with Java!
$ rbenv init
# creates a .ruby-version file in local dir truffleruby high performance version of jruby

$ rbenv local 3.1.2


# sets local version in .ruby-version file
Where all your Ruby versions are stored
$ rbenv versions
# shows installed ruby versions on machine $ ls ~/.rbenv/versions/
2.6.10
2.7.6
3.0.4 Your project's local Ruby version
3.1.2
~/…/.ruby-version rbenv uses this file to know which
jruby-9.3.7.0
version of ruby to use in this folder
mruby-3.1.0 3.1.2
picoruby-3.0.0
rbx-5.0
truffleruby-22.2.0
truffleruby+graalvm-22.2.0 7
Running Ruby Processes

Create Ruby file (*.rb)


script.rb
print 'Enter your first and last name: '
gets accepts user input from keyboard
fullname = gets.chomp
chomp removes trailing newline characters
puts "Hello there #{fullname}, let's search the Internet about you (see browser)"

def website(names)
Method (function) to create search URL
search_terms = names.gsub(/[^a-zA-Z0-9]/, '+')
"https://www.google.com/search?q=#{search_terms}"
end

`open #{website(fullname)}` Backticks (`…`) run Unix system command.

‘open’ is a Unix command to open a resource


with the default OS application for it.

Run Ruby file as new application process in Unix


$ ruby script.rb

Enter your first and last name: Soumya Ray


Hello there Soumya Ray, let's search the Internet about you (see browser)

Process dies when application ends

8
Code: https://github.com/ISS-SOA/demo_ruby_basics/blob/master/script.rb
Gems and Bundler
Gems 3rd party libraries for Ruby applications Hosted at: rubygems.org

Install them once on your local machine:

$ gem install <gem_name>

Stored together for current version of Ruby:


$ ls ~/.rbenv/versions/3.1.2/lib/ruby/gems/

Bundler
Gemfile.lock
$ gem install bundler Must install bundler gem once
GEM
remote: https://rubygems.org/
specs:
Gemfile
addressable (2.5.2)
source 'https://rubygems.org' public_suffix (>= 2.0.2, < 4.0)
...
$ bundle install http (2.2.2)
gem 'http'
List all gem dependencies in Gemfile addressable (~> 2.3)
http-cookie (~> 1.0)
http-form_data (~> 1.0.1)
http_parser.rb (~> 0.6.0)
http-cookie (1.0.3)
myapp.rb
domain_name (~> 0.5)
require 'http' ...
Use a gem in your code using require
PLATFORMS
# ... do something here ... Full list of all dependent gems and
ruby
their versions used for this project
DEPENDENCIES
$ bundle exec [...] Run a Ruby process using only gem versions in Gemfile.lock http

BUNDLED WITH
$ bundle exec ruby unidiomatic.rb https://soumyaray.com soumya.html 1.14.6 9
• allows you to write and evaluate code one line at a time
REPL • only available for dynamic languages (Python, Ruby, JS)

Read Execute Print Loop

IRB Pry
(default interactive ruby shell) (alternative ruby shell that can be installed as a gem)

$ irb
$ gem install pry
> puts 'hello'
hello $ pry
=> nil [1] pry(main)> def greet(name)
[1] pry(main)* puts "Hello #{name}!"
> Time.now [1] pry(main)* end
=> 2014-09-21 16:33:45 +0800 => :greet
[2] pry(main)> greet('Soumya')
> def greet(name) Hello Soumya!
?> puts "Hello #{name}!" => nil
?> end
=> :greet
“Rubyists, It’s Time to PRY Yourself Off IRB!”
> greet('Soumya’) http://www.sitepoint.com/rubyists-time-pry-irb/
Hello Soumya!
=> nil

Ruby is powerful -- you can even write your own REPL!

loop {
print("myrepl> ")
puts(eval(gets))
10
}
Ruby
“natural, not simple”

Yukihiro “Matz” Matsumoto

Object Oriented Message Passing Web Architecture Oriented


5 + 3 Everything is an object, so even Rack Ruby is most often used for back-
5.+ 3 operators (+, -, /) are methods Rails end web development, so there are
Roda plenty of 'frameworks' to organize
myarray[3] Arrays are also objects, so accessing code for web applications
myarray.[](3) elements is a method call Sinatra
Padrino

Expressive and readable


# we don't need to comment readable code
3.times { puts "Hip Hip Hooray!" } Strong Community
Code Style Languages belong to the
Functional paradigms Architecture Debates community of developers
who use it – they will often
search_engines = %w[Google Yahoo MSN] Rival Frameworks
debate how to use it and
Talks and Conferences what its future will be like
# we prefer not to use loops if possible Gurus and Bloggers
search_engines.map do |engine|
"http://www.%{engine.downcase}.com"
end
11
Methods
functions that belong to objects
Input

class Department Parameters


# ... other code
def find_students(id_list, options)
students = @all_students.select do |student|
id_list.include? student.id
end

log_event(:search, [id_list, options]) Side Effects : external changes from running code
• Database writes
if options • File writes
# handle options • Network messages sent
end • Screen output, etc.

Last line is return value


students Output
end
end

student_list = find_students(['678454', '223494'], {active: false, max: 30})

Arguments (values passed in for the parameters)


12
Idiom (成語): group of words having special meaning
(e.g., “raining cats and dogs”)

Idiomatic Ruby
Idiomatic: characteristic of a particular language or dialect

Unusual Ruby Code Idiomatic Ruby

Single-line conditional body not preferred… Using if as a modifier is preferred


if age < 0
puts "Please enter a positive age" puts "Please enter a positive age" if age < 0
end

Returning from a one-line if is not preferred… Prefer using a guard clause for returns
if choice == 'exit'
return false return false if choice == 'exit'
else # Do your special thing here!
# Do your special thing here!
end

Negative conditions not preferred… Prefer using unless


puts 'error' if !valid puts 'error' unless valid

13
Closures: Blocks

A closure is a function that is "closed over" it’s free variables Blocks with arguments
source: http://www.skorks.com/2010/05/closures-a-simple-explanation-using-ruby/

• Anonymous (no name) functions


• Can be passed like an object to others def func(x, y)
• Can be run at a later time if block_given? check if a block is provided
• Has copy of all variables in its scope when it was created result = yield(x, y) block yielded to with args x, y
else
result = x * y
Blocks: A type of closure that is passed as a method parameter
end

def my_method "Result: #{result}"


puts 'Starting my method' end
yield
yield runs block of code; has copy of status
puts 'Ending my method' func(2.0, 3.0)
end => "Result: 6.0"

status = 'Running the block…' func(2.0, 3.0) { |num1, num2| num1 / num2 }
=> "Result: 0.6666666666666666"
my_method { puts status; sleep(1) } Block of code sent to my_method
with all variables in current scope (status)
Starting my method Note: code should be shortened to idiomatic Ruby:
Running the block Note: error thrown if no block provided def func(x, y, &strategy)
Ending my method my_method result = block_given? ? yield(x, y) : x * y
=> nil # Starting my method "Result: #{result}"
# LocalJumpError: no block given (yield) end

14
read more: http://www.eriktrautman.com/posts/ruby-explained-blocks-procs-and-lambdas-aka-closures
Procedural loops: code for machines

Procedural loops are created to optimize assembly language compilation


we use them when we need to tell the machine how to loop

Procedural loops: for, do-while, while Assembly language loops

for(int x = 0; x<=3; x++) { xor cx,cx ; x = 0


//Do something! loop1 nop ; Do something!
} inc cx ; x++
cmp cx,3 ; x <= 3 ?
jle loop1 ; loop while condition met

int x=1; mov ax,1


do { loop1 nop ; Do something!
//Do something! cmp ax,1 ; x == 1 ?
} while(x==1) je loop1 ; loop while condition met

while(x==1) { jmp loop1 ; start while loop


//Do something! cloop1 nop ; Do something!
} loop1 cmp ax,1 ; x == 1 ?
je cloop1 ; loop while condition met

15
http://stackoverflow.com/questions/28665528/while-do-while-for-loops-in-assembly-language-emu8086
Functional iteration with blocks
arr = [2,3,1,6,4,9]

arr.each { |n| puts "number #{n}" } arr.select { |n| n.odd? }

2 3 1 6 4 9 2 3 1 6 4 9 3 1 9
Use for side effects
number 2
number 3
select
number 1
each number 6
number 4
number 9 Use for filtering

arr.map { |n| n * 2 } arr.reduce { |n1, n2| "#{n1} - #{n2}" }

2 3 1 6 4 9 4 6 2 12 8 18 2 3 1 6 4 9 "2 - 3 - 1 - 6 - 4 - 9"

map reduce

Use for transformations Use for summarization

Java 8 – No more loops: http://www.deadcoderising.com/java-8-no-more-loops/


Swift – Guide to Map Filter Reduce: http://useyourloaf.com/blog/swift-guide-to-map-filter-reduce/
16
Rust – Mapping Iterations: https://doc.rust-lang.org/std/iter/struct.Map.html
D – Performing Map, Filter and Reduce: https://mmstickman.wordpress.com/2015/01/29/performing-map-filter-and-reduce-in-d/
No more for loops
code for humans: idiomatic functional map/reduce
sites = %w{slack facebook github codecademy canvaslms}

for loop iteration functional iteration


For loops often obscure the intention of the iterative process Functional iteration clearly states its purpose
Readers must understand the full code Readers do not have to understand the entire code

titles = [] titles = sites.?????? do |site|


for site in sites html = open("https://www.#{site}.com").read
html = open("https://www.#{site}.com").read puts "#{site} scanned"
puts "#{site} scanned" capture = html.match(/<title>(?<title>.*)<\/title>/)
capture = html.match(/<title>(?<title>.*)<\/title>/) capture ? capture[:title] : nil
titles << capture ? capture[:title] : nil end
end

for title in titles titles.?????? { |title| puts title if title }


puts title if title
end

longest_title = '' longest_title = titles.??????('') do |t1, t2|


for title in titles (t2.length > t1.length) ? t2 : t1
longest_title = title end
if (title && (title.length > longest_title.length))
end

17
Code: https://github.com/ISS-SOA/demo_ruby_basics/tree/master/iteration
No more for loops
code for humans: idiomatic functional map/reduce
sites = %w{slack facebook github codecademy canvaslms}

for loop iteration functional iteration


For loops often obscure the intention of the iterative process Functional iteration clearly states its purpose
Readers must understand the full code Readers do not have to understand the entire code

titles = [] titles = sites.map do |site|


for site in sites html = open("https://www.#{site}.com").read
html = open("https://www.#{site}.com").read puts "#{site} scanned"
puts "#{site} scanned" capture = html.match(/<title>(?<title>.*)<\/title>/)
capture = html.match(/<title>(?<title>.*)<\/title>/) capture ? capture[:title] : nil
titles << capture ? capture[:title] : nil end
end

for title in titles titles.each { |title| puts title if title }


puts title if title
end

longest_title = '' longest_title = titles.reduce('') do |t1, t2|


for title in titles (t2.length > t1.length) ? t2 : t1
longest_title = title end
if (title && (title.length > longest_title.length))
end

18
Code: https://github.com/ISS-SOA/demo_ruby_basics/tree/master/iteration
Style Guide: Wisdom of the Crowds*
Ruby Style Guide: https://github.com/rubocop-hq/ruby-style-guide
* developed by open source review by the Ruby community

Example style rule: Example Ruby file:

Avoid use of nested conditionals for flow of control. unidiomatic.rb


require 'http'
Prefer a guard clause when you can assert invalid
data. A guard clause is a conditional statement at the def save_url(url_string, filename)
top of a function that bails out as soon as it can. if url_string
if filename
if File.exists?(filename)
raise ArgumentError, 'filename must be provided'
else
begin
html = HTTP.get(url_string)
File.write(filename, html)
rescue StandardError => error
raise IOError, error
end
end
Why did programmers else
raise ArgumentError, 'filename must be provided'
make this style rule? end
else
raise ArgumentError, 'url_string must be provided'
end

html
end
How can we identify
site_url = ARV[0]
filename = ARV[1] violations in our code?
save_url(site_url, filename)

19

Code: https://github.com/ISS-SOA/demo_ruby_basics/tree/master/idiomatic
Static Code Analysis
also known as “Linting”
https://en.wikipedia.org/wiki/Lint_%28software%29

$ rubocop unidiomatic.rb
Inspecting 1 file
W

$ gem install rubocop Offenses:

unidiomatic.rb:4:1: C: Method has too many lines. [19/10]


def save_url(url_string, filename) ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
unidiomatic.rb:4:1: C: Perceived complexity for save_url is too high. [8/7]
def save_url(url_string, filename)
^^^
unidiomatic.rb:5:3: C: Use a guard clause instead of wrapping the code inside
a conditional expression.
Symbol Description if url_string
^^
. no issues unidiomatic.rb:6:5: C: Use a guard clause instead of wrapping the code inside
a conditional expression.
C convention / style
if filename
E error ^^
unidiomatic.rb:7:7: C: Use a guard clause instead of wrapping the code inside
W warning a conditional expression.
if File.exists?(filename)
^^
unidiomatic.rb:7:15: W: File.exists? is deprecated in favor of File.exist?.
if File.exists?(filename)
^^^^^^^
unidiomatic.rb:13:9: C: Avoid more than 3 levels of block nesting.
rescue StandardError => error ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20
1 file inspected, 7 offenses detected
Linter Extensions

Extensions on your editor may allow rubocop to lint your code in real time

Extensions

Extension ruby-rubocop-revived seems to work

21
Refactoring
(changing appearance of code without changing behavior)
Unidiomatic Idiomatic
require 'http' # frozen_string_literal: true

def save_url(url_string, filename) require 'http'


if url_string
if filename def save_url(url_string, filename)
if File.exists?(filename) raise ArgumentError, 'URL must be provided' unless url_string
raise ArgumentError, 'file already exists' raise ArgumentError, 'file name must be provided' unless filename
else raise IOError, 'file already exists' if File.exist?(filename)
begin
html = HTTP.get(url_string) html = HTTP.get(url_string).body.to_s
File.write(filename, html) File.write(filename, html)
rescue StandardError => error html
raise IOError, error rescue StandardError => e
end raise IOError, e
end end
else
raise ArgumentError, 'file name must be provided' site_url, filename = ARGV
end save_url(site_url, filename)
else
raise ArgumentError, 'valid URL must be provided'
end

html
Is the idiomatic version clearer to read?
end Is the idiomatic version easier to change?

site_url = ARGV[0] How can we be sure that refactored


filename = ARGV[1] version does not change behavior? 22
save_url(site_url, filename)
Refactoring

small refactoring steady, small refactorings


+
develop new code
new code

Features
Refactoring allows you to
easily add new features

Refactoring + Developing allows you to


have steady, predictable progress

you competitor
Time

23
Refactoring vs. Rewriting

Won't repeat bugs/mistakes from old code Take advantage of new tools and technologies

Won't lose time visibly innovating Take advantage of new talent in the market

Don't have to maintain old + new code New developers may appreciate a fresh start

Don't risk creating a worse outcome! Just cannot maintain old codebase any more

Don't give competitors time to grow! Not a core / public product

24
Assignment 2: Ruby Intermediate

Continue Linux Tutorial (optional)


• Tutorial Three
• Tutorial Four Continue Ruby tutorial
5. Blocks and Sorting
•Methods, Blocks, & Sorting
•Ordering Your Library
6. Hashes and Symbols
•Hashes & Symbols
•A Night at the Movies
7. Refactoring
Write a better FizzBuzz! •The Zen of Ruby
•The Refactor Factory
• Apply what you have learned 8. Blocks, Procs, and Lambdas
• Write an algorithm •Blocks, Procs, and Lambdas

Use Rubocop for Idiomatic Style Quiz


• Clear all warnings on your code • Reading: CodingHorror
• Report questions/concerns on Slack • Research: Idiomatic style in other languages

25

You might also like