Fitting code on screen
makes developers happy;
slides.com/markburns-1/fitting-code-on-screen
A hypothesis
slides.com/markburns-1/fitting-code-on-screen
Time spent writing a haiku
Connascence
SOLID packaging principles
Problems
Tooling
Topics
slides.com/markburns-1/fitting-code-on-screen
Connascence
con + nascent
(kəˈneɪsənt)
(kön-nās'sénse)
born, produced, or growing simultaneously
slides.com/markburns-1/fitting-code-on-screen
Jim Weirich
Connascence, Coupling, Cohesion
Three sides of the same coin
slides.com/markburns-1/fitting-code-on-screen
- Multiple organisations' codebases
- Single organisation's codebases
- A tangled web of monolithic applications
- Mega service
- Monolithic web application
- Large web application
- Simple Web application
- Medium sized service
- Micro-service
- Complex CLI with sub commands
- Ruby gem/golang/Java package/Directory
- Script with a few files
- Ruby namespace
- Multi line bash script with functions
- Ruby file
- Ruby class
- Ruby method
- Ruby block/proc
- Line of code/ Lexical scope
- One line bash script
slides.com/markburns-1/fitting-code-on-screen
increasing size/complexity
increasing
risk of change
increasingly less desirable as a software dependency
increasing potential to deliver valuable features
increasing potential to deliver expensive features
Cohesion
"concerned with the grouping of functionally related [business] processes into a particular module"
slides.com/markburns-1/fitting-code-on-screen
Disambiguation added to separate from concept of OS processes
Cohesion
grouping related code
slides.com/markburns-1/fitting-code-on-screen
Fitting related code on screen
slides.com/markburns-1/fitting-code-on-screen
Coupling
"the flow of information or parameters passed between modules."
slides.com/markburns-1/fitting-code-on-screen
Coupling
connections between components
slides.com/markburns-1/fitting-code-on-screen
Connascence
"two components are connascent if a change in one would require the other to be modified in order to maintain the overall correctness of the system"
slides.com/markburns-1/fitting-code-on-screen
Connascence
slides.com/markburns-1/fitting-code-on-screen
connections between components
Connascence
slides.com/markburns-1/fitting-code-on-screen
connections inside components
slides.com/markburns-1/fitting-code-on-screen
Coupling (between modules)
Cohesion
(Bad) Connascence
(Good) Connascence
class B1
Connascence
class A1
NamespaceA
class A2
NamespaceB
Some coupling is necessary
slides.com/markburns-1/fitting-code-on-screen
Cohesion
(Good) Connascence
class B1
Connascence
class A1
NamespaceA
class A2
NamespaceB
Some coupling is necessary
Fitting related code on screen
slides.com/markburns-1/fitting-code-on-screen
Cohesion
Connascence
Coupling
Connascence
Within a component
Across components
Cohesion
Connascence
Coupling
Connascence
Coupling
Connascence
Within a component
Across components
Cohesion
Connascence
products.map { |product| [product.name, product.type, product.price, product.id] }
products.map { |p| [p.name, p.type, p.price, p.id] }
slides.com/markburns-1/fitting-code-on-screen
Fitting related code on screen
slides.com/markburns-1/fitting-code-on-screen
SomeMapApi::Location.lookup_by(ip_address: ip_address)
Location.new(ip_address)
slides.com/markburns-1/fitting-code-on-screen
$(".we-probably-do-this-a-lot")
new(jQuery).domAgnosticLookupBySelector(".if-it-was-harder-to-do-we-wouldnt-do-this-as-often")
slides.com/markburns-1/fitting-code-on-screen
slides.com/markburns-1/fitting-code-on-screen
Conciseness enables ubiquity
slides.com/markburns-1/fitting-code-on-screen
to be
I
You
She
I
She
They
am
are
is
was
was
were
puts File.read(ARGV[0])
package com.example;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public class PrintString {
public static void main(String[] args) {
File file = new File(args[0]);
try {
String content = FileUtils.readFileToString(file);
System.out.println(content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
slides.com/markburns-1/fitting-code-on-screen
Product.where(name: name)
slides.com/markburns-1/fitting-code-on-screen
Products::Repository.by_name(name)
slides.com/markburns-1/fitting-code-on-screen
S - Single responsibility O - Open-closed L - Liskov substitution I - Interface segregation D - Dependency inversion
slides.com/markburns-1/fitting-code-on-screen
SOLID - A recap
"In Rails you don't really have application architecture."
slides.com/markburns-1/fitting-code-on-screen
slides.com/markburns-1/fitting-code-on-screen
slides.com/markburns-1/fitting-code-on-screen
MVCPDSS
slides.com/markburns-1/fitting-code-on-screen
😢 Repositories? 😞
slides.com/markburns-1/fitting-code-on-screen
The 6 lesser known SOLID principles
Common Closure
Common Reuse
Acyclic Dependencies
Stable Abstractions
Stable Dependencies
Release Reuse Equivalence
Common Reuse Principle
Depend upon all classes in a package.
Classes that are re-used together belong together
slides.com/markburns-1/fitting-code-on-screen
Release Reuse Equivalence Principle
The granule of reuse is The granule of release
slides.com/markburns-1/fitting-code-on-screen
Common Closure Principle
Classes that change together belong together
slides.com/markburns-1/fitting-code-on-screen
Cohesion
(Good) Connascence
Connascence
class A1
NamespaceA
class A2
Acyclic Dependencies Principle
Packages should not have cyclic dependencies
slides.com/markburns-1/fitting-code-on-screen
Stable Abstractions Principle
Packages that are more abstract should be more stable
slides.com/markburns-1/fitting-code-on-screen
Stable Dependencies Principle
Depend on packages in same direction as direction of stability
slides.com/markburns-1/fitting-code-on-screen
CASE
A CONTRIVED BACKRONYM
C ————— Common Closure \_____ Common Reuse
A ————— Acyclic Dependencies
S ————— Stable Abstractions \_____ Stable Dependencies
E ————— Release Reuse Equivalency
slides.com/markburns-1/fitting-code-on-screen
CASE
A CONTRIVED BACKRONYM
Cohesion ___ Common Closure \__ Common Reuse
Acyclicity __ Acyclic Dependencies
Stability ___ Stable Abstractions \__ Stable Dependencies
Equivalence _ Release Reuse Equivalency
slides.com/markburns-1/fitting-code-on-screen
CASE
A CONTRIVED BACKRONYM
Cohesion ___ Common Closure \__ Common Reuse
Equivalence _ Release Reuse Equivalence
Acyclicity __ Acyclic Dependencies
Stability ___ Stable Abstractions \__ Stable Dependencies
slides.com/markburns-1/fitting-code-on-screen
Cohesion
Packaging
Disambiguation of
Abstract/Concrete, Generic/Specific
Application
specific
concrete
higher level
Library
generic
abstract
level
slides.com/markburns-1/fitting-code-on-screen
Product
ActiveRecord::Base
Library vs Application?
Application logic
Less stable, less generic
More specific
Library code
More stable, more generic
Less specific
Dependency Direction
slides.com/markburns-1/fitting-code-on-screen
Fitting related code on screen
slides.com/markburns-1/fitting-code-on-screen
slides.com/markburns-1/fitting-code-on-screen
Happy Rails Land
class Blog < ApplicationRecord
belongs_to :author,
class_name: :User,
inverse_of: :blogs,
foreign_key: :author_id
has_many :posts
end
class Post < ApplicationRecord
belongs_to :blog
has_many :comments
belongs_to :author,
through: :blog
end
class Comment < ApplicationRecord
belongs_to :post
belongs_to :author,
class_name: :User
end
class User < ApplicationRecord
has_many :blogs,
inverse_of: :author,
foreign_key: :author_id
has_many :posts, through: :blogs
has_many :comments
end
slides.com/markburns-1/fitting-code-on-screen
Happy Rails Land
▾ models/
▸ concerns/
application_record.rb
blog.rb
comment.rb
post.rb
user.rb
slides.com/markburns-1/fitting-code-on-screen
Happy Rails Land
▾ views/
▸ blogs/
▾ comments/
_comment.json.jbuilder
_form.html.erb
edit.html.erb
index.html.erb
index.json.jbuilder
new.html.erb
show.html.erb
show.json.jbuilder
▸ layouts/
▾ posts/
_form.html.erb
_post.json.jbuilder
edit.html.erb
index.html.erb
index.json.jbuilder
new.html.erb
show.html.erb
show.json.jbuilder
▸ users/
▾ stylesheets/
application.css
blogs.scss
comments.scss
posts.scss
scaffolds.scss
users.scss
slides.com/markburns-1/fitting-code-on-screen
React!!!1!
slides.com/markburns-1/fitting-code-on-screen
React!!!1!
Forget Everything You Know About Web Development
slides.com/markburns-1/fitting-code-on-screen
React!!!1!
Problems with CSS at scale
- Global Namespace
- Dependencies
- Dead Code Elimination
- Minification
- Sharing Constants
- Non-deterministic Resolution
- Isolation
slides.com/markburns-1/fitting-code-on-screen
React!!!1!
Components
- Markup
- Styling
- Functionality
slides.com/markburns-1/fitting-code-on-screen
React!!!1!
Keeping change local
VS
Separation of concerns
slides.com/markburns-1/fitting-code-on-screen
React!!!1!
Local markup, code and styling in one file
is equivalent to:
products.map { |p| [p.name, p.id] }
slides.com/markburns-1/fitting-code-on-screen
Components
Keeping change local
AND
Separation of concerns
=
Low Connascence
We've been building websites wrong?
MVC/PDIS...
slides.com/markburns-1/fitting-code-on-screen
app/models app/views assets/javascripts assets/stylesheets
Ruby HTML JS CSS
Slicing by design pattern
Slicing by technology
Slicing at concept boundary
products
orders
users
payments
slides.com/markburns-1/fitting-code-on-screen
Slicing at concept boundary
▾ products/
▾ components/
product.js
product.spec.js
▾ views/
edit.html.slim
index.html.slim
new.html.slim
show.html.slim
presenter.rb
product.rb
products_controller.rb
slides.com/markburns-1/fitting-code-on-screen
organised by technology/design pattern
Slicing at concept boundary
▾ study/
▸ badges/
▾ controllers/
mnemonics_controller.rb
study_items_controller.rb
study_lists_controller.rb
study_sessions_controller.rb
▾ models/
mnemonic.rb
session.rb
study_item.rb
study_list.rb
study_session.rb
▸ views/
item.rb
item_decorator.rb
items_helper.rb
mnemonic_decorator.rb
mnemonic_persistence.rb
mnemonic_repository.rb
mnemonics_helper.rb
study_item_decorator.rb
study_item_helper.rb
study_session_decorator.rb
study_session_helper.rb
slides.com/markburns-1/fitting-code-on-screen
Slicing at concept boundary
▾ study/
▸ badges/
▾ items/
▸ views/
decorator.rb
helper.rb
item.rb
list.rb
items_controller.rb
lists_controller.rb
slides.com/markburns-1/fitting-code-on-screen
▾ study/
▸ badges/
▾ items/
▾ mnemonics/
▸ views/
mnemonic.rb
mnemonics_controller.rb
decorator.rb
helper.rb
persistence.rb
repository.rb
▾ study/
▸ badges/
▸ items/
▸ mnemonics/
▾ sessions/
▸ views/
session.rb
session.rb
decorator.rb
helper.rb
study_sessions_controller.rb
module DomainConcepts
def self.prepended(base)
base.class_eval do
before_action :set_view_paths
end
end
%i(index show edit new).each do |a|
define_method a do
super()
render_template a
end
end
private
def set_view_paths
prepend_view_path Rails.root.join("app/concepts/#{namespace}/views")
end
def namespace
self.class.name.deconstantize
end
def render_template(*args)
options = args.extract_options!
options[:template] = params[:action]
render(*(args << options))
end
end
module Study
class ListsController < ApplicationController
prepend DomainConcepts
end
end
module Study
class ItemsController < ApplicationController
prepend DomainConcepts
end
end
resource :lists, module: :study
resource :items, module: :study
Things that don't fit?
-
Generic/Library
-
Cross domain concept
slides.com/markburns-1/fitting-code-on-screen
Generic code
▾ assets/
▾ components/
button.js
button.spec.js
calendar.js
calendar.spec.js
▾ models/
▾ concerns/
slugification.rb
slides.com/markburns-1/fitting-code-on-screen
▾ lib/
▸ assets/
▸ tasks/
decorator.rb
presenter.rb
▾ app/
▾ models/
category.rb
navigation_item.rb
▾ presenters/
presenter.rb
navigation_presenter.rb
▾ decorators/
decorator.rb
api_decorator.rb
Products
Search
ProductSearchResultPresenter??
Which domain category?
slides.com/markburns-1/fitting-code-on-screen
Dilemma
Products
Search
ProductSearchResultPresenter??
slides.com/markburns-1/fitting-code-on-screen
Dilemma
Products
Search
ProductSearchResultPresenter??
slides.com/markburns-1/fitting-code-on-screen
No Dilemma
Products
Search
ProductSearch::ResultPresenter
ProductSearch
slides.com/markburns-1/fitting-code-on-screen
slides.com/markburns-1/fitting-code-on-screen
S-expressions
slides.com/markburns-1/fitting-code-on-screen
S-expressions
puts "Hello"
require "pp"
require "ruby_parser"
hello = File.read "hello.rb"
sexp = RubyParser.new.parse(hello)
pp sexp
s(:call, nil, :puts, s(:str, "Hello"))
slides.com/markburns-1/fitting-code-on-screen
S-expressions
Kernel.puts "Hello"
require "pp"
require "ruby_parser"
hello = File.read "hello.rb"
sexp = RubyParser.new.parse(hello)
pp sexp
(:call, s(:const, :Kernel), :puts, s(:str, "Hello"))
slides.com/markburns-1/fitting-code-on-screen
S-expressions
class Product
def initialize(name)
@name = name
local_variable = "something else"
end
end
Product.new.name
require "pp"
require "ruby_parser"
product = File.read "product.rb"
sexp = RubyParser.new.parse(product)
pp sexp
s(:block,
s(:class,
:Product,
nil,
s(:defn,
:initialize,
s(:args, :name),
s(:iasgn, :@name, s(:lvar, :name)),
s(:lasgn, :local_variable, s(:str, "something else")))),
s(:call, s(:call, s(:const, :Product), :new), :name))
slides.com/markburns-1/fitting-code-on-screen
S-expressions
class Product
def initialize(name)
@name = name
something = "something else"
end
end
Product.new.name
require "pp"
require "ruby_parser"
product = File.read "product.rb"
sexp = RubyParser.new.parse(product)
pp sexp
s(:block,
s(:class,
:Product,
nil,
s(:defn,
:initialize,
s(:args, :name),
s(:iasgn, :@name, s(:lvar, :name)),
s(:lasgn, :something, s(:str, "something else")))),
s(:call, s(:call, s(:const, :Product), :new), :name))
slides.com/markburns-1/fitting-code-on-screen
S-expressions
- Find all English keywords in a codebase
- Stem them
- If the file path contains the keyword -> OK
- No match on file path -> Possible code smell?
slides.com/markburns-1/fitting-code-on-screen
S-expressions
./bin/analysis "../manageiq/app/**/*.rb"
4069 Host
4060 Log
3655 Miq
3523 Node
3521 Group
3392 Button
3177 Field
3172 Page
2661 Tree
2643 User
2541 Tag
2510 Event
2498 Resourc
2454 Storag
2427 Session
2278 Server
2267 Flash
2108 Target
2047 Provid
2040 Task
2038 Info
2035 Image
1937 Descript
1921 Messag
1911 Templat
1851 Parent
1776 Polici
1724 Msg
1711 Dialog
1694 Refresh
1680 Contain
1661 Report
1617 Div
1568 Servic
1562 Manag
1541 Count
1499 Label
1483 Last
1480 Profil
1447 Exp
1433 Perf
1430 Start
1403 Col
1374 Collect
1369 Lookup
1368 Replac
1334 Titl
1318 List
1310 Manageiq
1254 Cloud
1250 The
1243 Textual
1226 Disk
1224 Metric
1195 Obj
1194 Rec
1192 Custom
1099 Pficon
1094 Err
1094 Virtual
1091 Split
1076 Tenant
1075 Configur
1072 Text
1064 Cell
1048 Thi
1044 Userid
1038 Check
1027 Interval
1026 View
1020 Schedul
1017 Vmdb
1015 Form
990 Cluster
972 Network
957 Right
953 Operat
948 Availabl
943 Cpu
941 Val
929 Worker
907 Process
899 Klass
895 Javascript
874 Layout
868 Inv
864 Uid
859 Rpt
858 Tab
854 Folder
851 Queue
834 Toolbar
826 Snapshot
826 Push
826 Total
824 Typ
813 Volum
810 Condit
809 Pxe
809 Idx
slides.com/markburns-1/fitting-code-on-screen
S-expressions
slides.com/markburns-1/fitting-code-on-screen
S-expressions
class Product < ApplicationRecord
end
class UserProfilesController
def recommended_product
@product.name
end
end
s(:class,
:UserProfilesController,
nil,
s(:defn,
:recommended_product,
s(:args),
s(:call, s(:ivar, :@product), :name)))
Delfos
slides.com/markburns-1/fitting-code-on-screen
Runtime Type Recording
slides.com/markburns-1/fitting-code-on-screen
slides.com/markburns-1/fitting-code-on-screen
gem "delfos", "~> 0.0.1"
#config/initializers/000_delfos.rb
Delfos.setup!(application_directories: %w(app lib))
# begins adding method logging to methods defined
# after this point (in app and lib) during runtime.
github.com/markburns/delfos
slides.com/markburns-1/fitting-code-on-screen
slides.com/markburns-1/fitting-code-on-screen
MATCH
(c1:Class)-[:OWNS] ->(m1:Method)
-[:CONTAINS]->(cs:CallSite)
-[:CALLS] ->(m2:Method),
(c2:Class)-[:OWNS] ->(m2)
-[:CONTAINS]->(cs2:CallSite)
-[:CALLS] ->(m3:Method)
<-[:OWNS]-(c1)
WHERE c2 <> c1
RETURN c1,c2,cs,m1
slides.com/markburns-1/fitting-code-on-screen
Delfos
- Query to find cyclic dependencies
- Find dead code
- Find candidate code for colocation/namespacing
- Visualize each step through your codebase
- Do more than fix just superficial cyclomatic complexity
- Find other architectural problems with your code
github.com/markburns/delfos
slides.com/markburns-1/fitting-code-on-screen
Delfos - future
- Improve performance
- Simplify setup in e.g. CI environments
- Sit behind load balancer receiving duplicate requests to record real production call stacks.
- Create tools that use Delfos
- Automatic refactor suggestions
- Pull request bot
- Think of other creative uses for querying the neo4j data
github.com/markburns/delfos
slides.com/markburns-1/fitting-code-on-screen
Questions?
Real world limitations
slides.com/markburns-1/fitting-code-on-screen
slides.com/markburns-1/fitting-code-on-screen
DonutTopping
DonutFilling
DonutCategory
Donut -> ChocolateDonut
Donut -> MapleSyrupDonut
Donut -> ZebraStripedDonut
MonsterLarge
MonsterScary
MonsterSmall
PersonLarge
PersonSmall
Zombie
harder to locate on the file system
has_a?
is_a? HACKS
LargeMonster
LargePerson
ScaryMonster
SmallMonster
SmallPerson
Zombie
easier to locate on the file system
is_a?
slides.com/markburns-1/fitting-code-on-screen
THE DICHOTOMY
ONE FILE
LOTS OF OBJECTS
“Easy to see where things are”
“Easy to understand”
“Tangled”
“God objects”
“Difficult to understand”
"Heard of the SRP?"
"Spaghetti"
“Simple easy to test small objects”
“Easy to understand”
“Scattering tons of objects around the file system”
“Over engineering”
“Difficult to understand”
“Can only understand each piece in isolation”
“Can only understand one feature by reading tons of files”
"Spaghetti"
slides.com/markburns-1/fitting-code-on-screen
THE DICHOTOMY
ONE FILE
LOTS OF OBJECTS
“Easy to see where things are”
“Tangled”
“God objects”
"Heard of the SRP?"
“Simple easy to test small objects”
“Scattering tons of objects around the file system”
“Over engineering”
“Can only understand each piece in isolation”
“Can only understand one feature by reading tons of files”
“Easy to understand”
“Difficult to understand”
"Spaghetti"
“Easy to understand”
“Difficult to understand”
"Spaghetti"
slides.com/markburns-1/fitting-code-on-screen
IT'S NOT A DICHOTOMY
One File | Lots of Objects | Namespacing +Lots of objects | |
---|---|---|---|
Low Connascence | √ | x | √ |
SRP | x | √ | √ |
Low Cyclomatic Complexity | x | √ | √ |
Application directory structure scales well with feature addition | √ | x | √ |
slides.com/markburns-1/fitting-code-on-screen
THE DICHOTOMY
ONE FILE
LOTS OF OBJECTS
Complexity internal to file = Low Connascence across class boundaries
Obeying Cohesion Packaging Principles
BUT Breaking SRP
Simplicity within file = Low Cyclomatic Complexity
Obeying SRP
BUT Breaking Cohesion Packaging Principles
slides.com/markburns-1/fitting-code-on-screen
WHEN TO BREAK APART?
Number of models
Design Patterns
5
10
20
5
10
20
50
50
100
Over engineering
Little benefit
Increasing benefit to namespacing
Under engineering
Copy of Fitting code on screen
By Mark Burns
Copy of Fitting code on screen
- 387