Developing with Agility

What this talk is not about

  • Anti-waterfall
  • Agile™
  • Scrum
  • Kanban
  • Any other project
    management
    marketing terms

Agile™ Software Development

Coined by consultants

Scrum & Kanban are not bad or irrelevant; they provide frameworks for organizations

Not a requirement for developing with agility

Agile is not a noun. You can't do agile.

Agile Manifesto

No mention of sprints, backlog, etc

Our consultants did not mention the manifesto 18 months ago

Focus of this Talk

  • Welcome changing requirements, even late in development. Agile processes harness change for the customer's competitive advantage.
     
  • Continuous attention to technical excellence and good design enhances agility.
     
  • Simplicity--the art of maximizing the amount of work not done--is essential.
     
  • Agile processes promote sustainable development. The sponsors, developers, and users should be able to maintain a constant pace indefinitely.

Agile Fast

Agile = Consistent velocity in the face of change

SOLID & TRUE

SOLID Principles

Single Responsibility Principle

Open/Closed Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

Liskov Substitution Principle

Starting with "L"

...because I think it's pretty boring

Liskov Subsitution

Inheritance is not a prerequisite for Object-oriented design

Behavioral Subtyping

Single Responsibility Principle

Actually pretty interesting

Common Sense

Classes & methods should be focused on one thing

?

Same level of abstraction

public async Task<byte[]> DownloadFile(){
  var invoice = await _mediator.Send(new InvoiceQuery(carrier));
  var orderData = await _mediator.Send(new CustomerData(invoice.Id));
  var transformedInvoice = _invoiceService.Manipulate(invoiceData, orderData);
  var file = _invoiceService.CreateCsv(transformedInvoice);
  return file;
}

Open/Closed Principle

Open for Extension & Closed for Modification

How do we introduce new behavior without modifying existing code?

Design to an interface

Interface Segregation Principle

Clients should not be forced to depend upon interfaces that they do not use.

Dependency Inversion Principle

Methods vs Functions

Not just syntactic differences

Mathematically, a function is defined as "A special relationship where each input has a single output"

Functional Code = Testable Code

Testable code allows us to write tests

Tests allow us to be confident in our code integrity in the face of change

Confidence in our code integrity in the face of change enables us to Develop with Agility

How do we make code more Functional?

Pass in dependencies!

New is Glue - Steve Smith 2011

Quick Demo

Dependency Injection

"After having written nice, decoupled code throughout your code base, the Composition Root is where you finally couple everything, from data access to (user) interfaces." - Mark Seemann Composition Root Reuse

Unix Philosophy

Doug McIlroy (Bell System Technical Journal 1978)

  1. Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new "features".
  2. Expect the output of every program to become the input to another, as yet unknown, program. Don't clutter output with extraneous information. ... Don't insist on interactive input.

And a couple other principles that I don't think apply to developing with agility... https://en.wikipedia.org/wiki/Unix_philosophy

TRUE Principles

Transparent

The consequences of change should be obvious in the code that is changing and in distant code that relies upon it.

Related to Single Responsibility

Our methods should be able to stand on their own and be obvious.

Don't use mutable global variables

Reasonable

The cost of any change should be proportional to the benefits the change achieves.

Usable

Existing code should be usable in new and unexpected contexts.

Exemplary

The code itself should encourage those who change it to perpetuate these qualities.

Simple vs Easy

Simple Made Easy - Rich Hickey Strange Loop 2011

Domain Driven Design & Distributed Systems

Loops & Conditionals

Basic control structure

Bread & Butter

Should be avoided

Declarative vs Imperative

Imperative

Declarative

const numbers = [0,1,2,3,4,5,6,7,8,9,10]

const isEven = input => input % 2 === 0

const square = input => input * input

const getSum = (input1, input2) =>
  input1 + input2

let squaredEvens = [];
numbers.forEach(x => {
  if (isEven(x)) {
    squaredEvens.push(square(x))
  }
})

let sumSquaredEvens = 0
squaredEvens.forEach(x => 
  sumSquaredEvens = getSum(sumSquaredEvens, x))
const numbers = [0,1,2,3,4,5,6,7,8,9,10]

const isEven = input => input % 2 === 0

const square = input => input * input

const getSum = (input1, input2) =>
  input1 + input2

const sumSquaredEvens = numbers
                         .filter(isEven)
                         .map(square)
                         .reduce(getSum, 0)

Imperative

const otherFunc = (input1: string, input2: string) => input1 + " " + input1

const yeetFunc = input => "yeet! " + input
const coolFunc = (input1: string, input2: string) => {
  let str = ""
  if (input == "case1") {
    str = otherFunc(input1, input2)
  } else if (input === "case2") {
    str = otherFunc(input2, input1)
  } else {
    str = yeetFunc(input1)
  }
  console.log(str)
}

Declarative

const coolFunc = (input1: string, input2: string) => {
  const behavior = behaviorFactory.create(input1, input2)
  const str = behavior.execute()
  console.log(str)
}
class OtherBehavior implements IBehavior {
  input1: string
  input2: string
  
  constructor(input1, input2) {
    this.input1  = input1
    this.input2 = input2
  }
  
  execute = () => input1 + " " + input2
}

class YeetBehavior implements IBehavior {
  input: string
  
  constructor(input) {
    this.input = input
  }
  
  execute = () => "yeet! " + input
}
class BehaviorFactory {
  static create(input1, input2): IBehavior {
    if (input1 == "case1") {
      return new OtherBehavior(input1, input2)
    } else if (input1 == "case2") {
      return new OtherBehavior(input2, input1)
    } else {
      return new YeetBehavior(input1)
    }
  }
}
interface IBehavior {
  execute(): string
}

Kernighan's lever

Brian Kernighan - The Elements of Programming Style

"Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?"

Ben Hunt 2018

Premature Optimization

If you're concerned with performance but not measuring it, you're just guessing

Is the speed worth the cost?

Don't Invent Problems

Use someone else's solution if it's appropriate

Dapper, mediator, serialization tools, standard http verbs & response codes, logging, etc

Focus on Solving the Business Problem

Conclusion

  • Use SOLID & TRUE code
  • Focus on simple architecture--not necessarily easy architecture
  • Prefer declarative code over imperative code
  • Don't be clever
  • Solve business problems

Developoing with Agility

By joeybrown

Developoing with Agility

  • 88