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
  1. Multiple organisations' codebases
  2. Single organisation's codebases
  3. A tangled web of monolithic applications
  4. Mega service
  5. Monolithic web application
  6. Large web application
  7. Simple Web application
  8. Medium sized service
  9. Micro-service
  10. Complex CLI with sub commands
  11. Ruby gem/golang/Java package/Directory
  12. Script with a few files
  13. Ruby namespace
  14. Multi line bash script with functions
  15. Ruby file
  16. Ruby class
  17. Ruby method
  18. Ruby block/proc
  19. Line of code/ Lexical scope
  20. 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

  1. Global Namespace
  2. Dependencies
  3. Dead Code Elimination
  4. Minification
  5. Sharing Constants
  6. Non-deterministic Resolution
  7. Isolation
slides.com/markburns-1/fitting-code-on-screen

React!!!1!

Components

  1. Markup
  2. Styling
  3. 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?

  1. Generic/Library

  2. 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

  1. Query to find cyclic dependencies
  2. Find dead code
  3. Find candidate code for colocation/namespacing
  4. Visualize each step through your codebase
  5. Do more than fix just superficial cyclomatic complexity
  6. 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