Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Challenges in Software Development

Challenges in Software Development

Gives a glimpse of that Software Development is about, illustrated by 3 challenges:
- Avoiding misunderstandings using "Agile" methods
- Avoiding techical debt with clean code
- Delivering quality fast with automated tests

Sebastian Nozzi

November 22, 2013
Tweet

More Decks by Sebastian Nozzi

Other Decks in Programming

Transcript

  1. Problems with this approach • Not all details available at

    the beginning ➡ Many assumptions need to be made (possibly wrong ones) • Difficult to do accurate resource planing and time estimations for the whole process • Customer does not want what he/she needs until seeing final solution • Once design is made, it's difficult / costly to change
  2. Short Feedback Cycles As soon as there is something to

    show to the customer, show it, gather feedback and repeat cycle. Approach final solution gradually and iteratively.
  3. Increasing effort per version... Version1.0 Version1.1 Version1.2 LC LC LC

    LC LC LC ... thanks to accumulated legacy code ... also called "technical debt"
  4. def f(x): data = [] ... return data Names too

    abstract, impossible to know what they mean:
  5. def find_phd_students(ist_user_list): ... def find_postdocs(ist_user_list): ... def find_professors(ist_user_list): ... def

    find_staff(ist_user_list): ... def find_ssu_heads(ist_user_list): ... Re-writing similar functionality over and over again:
  6. def find_users(ist_user_list, user_type): ... phd_students = find_users(users, UserType.PHD) ssu_heads =

    find_users(users, UserType.SSU_HEAD) Generalise into re-usable code:
  7. def fetch_ist_users(only_active): # open configuration file # get database name,

    user and password # connect to the database # construct query # perform query for all users # iterate over result # if "only_active" is set, keep only active # return list -Doing too much -Mixing different levels of abstraction
  8. object Database: def connect(): # get configuration ... def get_all_users():

    ... object Configuration: def get_datbase_user(): ... def get_database_password(): ... def fetch_ist_users(only_active): # Obtain all users from Database # iterate over result # if "only_active" is set, keep only active # return list Separated concerns into re-usable components
  9. generic specific generic specific generic specific generic re-usable not re-usable

    not re-usable In general, de-compose your problem into specific and generic aspects. Otherwise the whole is not re-usable. You benefit in the future, by being able to leverage your re- usable components and cut cost&time...
  10. Goal: Version 1.1 Version1.0 Version1.1 Version1.2 We were given the

    task to implement Version 1.1, and we want time and costs not to increase...
  11. Feature Feature Fix Feature Version 1.0 Version 1.1 These are

    the features/fixes to implement... goal
  12. breaks? breaks? breaks? Feature Feature Fix Feature Version 1.0 Version

    1.1 But by each introduced change we might have broken something...
  13. It could even be like this... Feature Feature Fix Feature

    breaks? breaks? breaks? breaks? breaks?
  14. Possible Approaches • Prevent (static analysis, compile-time checking, etc.) •

    Analyse (review code / changes, hope to spot bugs) • Manual Tests (time-intensive, prone to human error) • Automated Tests...?
  15. Automated Tests • Write once (has some initial cost) •

    Run repeatedly (running is cheap / quick) • No need to manually track dependencies ➡ Developer productivity and program robustness
  16. Write a failing Test Make it Pass Improve your Code

    Start here Test Driven Development
  17. Benefits of "Test-first" • Forces one to state the requirements

    clearly, in advance+ • Focuses on one problem at a time ➡ Avoids designing too early for inexistent problems • Leads to better, re-usable, testable code • Leads to high test-coverage
  18. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) greeter_test.py greeter.py We begin by writing the test first
  19. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) greeter_test.py greeter.py Initially, the test fails because there is no implementation
  20. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) def greet(name): return "Hello, " + name greeter_test.py greeter.py Once there is a correct implementation, the test passes
  21. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): return "Hello, " + name greeter_test.py greeter.py We write a test for a further requirement, which initially fails
  22. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == ""): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py Once the implementation has been corrected to comply with this requirement, the test passes
  23. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == ""): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py def test("greet the world when nil"): expected = "Hello, World" result = greet(nil) assert(result == expected) One last requirement, and an initially failing test
  24. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == nil): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py def test("greet the world when nil"): expected = "Hello, World" result = greet(nil) assert(result == expected) ? Does this new implementation make all tests pass? ? ?
  25. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == nil): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py def test("greet the world when nil"): expected = "Hello, World" result = greet(nil) assert(result == expected) No, it actually breaks the second test. Note that we didn't need to analyse the whole problem. The testing framework does it for us.
  26. def test("greet Dave"): expected = "Hello, Dave" result = greet("Dave")

    assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == nil || name == ""): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py def test("greet the world when nil"): expected = "Hello, World" result = greet(nil) assert(result == expected) Once fixed, all tests pass
  27. Metrics of a Production App • Very complex workflows, lots

    of dependencies • 65 Testing Scenarios • 742 Automated Interactions • 117 Automated non-interactive Tests • Time for all tests: 1 minute • Manually: a whole day? two? Real time- saver!
  28. Recap •Short Feedback Cycles ... to avoid unpleasant surprises •Readable,

    Re-Usable Code ... to reduce cost per version •Automated Tests ... to introduce changes quickly and with confidence