0% found this document useful (0 votes)
85 views7 pages

Software Engineering: CS/ENGRD 2110 Object-Oriented Programming and Data Structures

Download as pdf or txt
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 7

13/02/2012

Software Engineering
CS/ENGRD 2110
Object-Oriented Programming • The art by which we start with a problem
and Data Structures statement and gradually evolve a solution.
Spring 2012
Thorsten Joachims
• There are whole books on this topic and most
companies try to use a fairly uniform
Lecture 7: Software Design approach that all employees are expected to
follow.

• The IDE can help by standardizing the steps.

Top-Down Design Bottom-Up Design


• Building a Search Engine: • Just the opposite: start with parts:
Search Engine Search Engine

User User
Crawler Indexer Crawler Indexer
Interface Interface

HTTP Inverted Ranking Spelling HTTP HTTP Inverted Ranking Spelling HTTP
Queue Database Queue Database
Client Index Function Correction Server Client Index Function Correction Server

Term Term
Hashmap List Pagerank Hashmap List Pagerank
Weighting Weighting

• Refine the design at each step • Composition


• Decomposition / “Divide and Conquer” • Build-It-Yourself (e.g. IKEA furniture)

Top-Down vs. Bottom-Up Software Process


• Is one of these ways better? Not really! • For simple programs, a simple process…
• It’s sometimes good to alternate
• By coming to a problem from multiple angles you might “Waterfall”
notice something you had previously overlooked
• Not the only ways to go about it

• With Top-Down it’s harder to test early because


parts needed may not have been designed yet • But to use this process, you need to be sure that
the requirements are fixed and well understood!
• With Bottom-Up, you may end up needing – Many software problems are not like that
things different from how you built them – Often customer refines the requirements when you try to
deliver the initial solution!

1
13/02/2012

Incremental & Iterative


• Deliver versions of the system in several small cycles:

• Recognizes that for some settings, software TESTING AND


development is like gardening.
• You plant seeds… see what does well… then replace the TEST-DRIVEN DEVELOPMENT
plants that did poorly.

The Importance of Testing The Example


• Famous last words • A collection class SmallSet
– “Its all done, I just have not tested it yet”.
• containing up to N objects (hence “small”)
• typical operations:
• Many people
– Write code without being sure it will work
add adds item
– Press run and pray contains is item in the set?
– If it fails, they change something random size # items
 Never work, and ruins weekend social plans.
• we’ll implement add(), size()
• Test-Driven Development!

Test Driven Development JUnit


• We’ll go about in small iterations • What do JUnit tests look like?
1.add a test SmallSet.java SmallSetTest.java
package edu.cornell.cs.cs2110; package edu.cornell.cs.cs2110;
2.run all tests and watch the new one fail
public class SmallSet { import org.junit.Test;
3.make a small change ... import static org.junit.Assert.*;
}
4.run all tests and see them all succeed public class SmallSetTest {
@Test public void testFoo() {
5.refactor (as needed) SmallSet s = new SmallSet();
...
assertTrue(...);
}
• We’ll use JUnit @Test public void testBar() {
...
}
}

2
13/02/2012

A List of Tests A First Test


• We start by thinking about how to test, • We pick a feature and test it:
SmallSet
not how to implement class SmallSet {}

• size=0 on empty set SmallSetTest


class SmallSetTest {
• size=N after adding N distinct elements @Test public void testEmptySetSize() {
• adding element already in set doesn’t change size SmallSet s = new SmallSet();
assertEquals(0, s.size());
• throw exception if adding too many }
}
• ...
• This doesn’t compile: size()is undefined
• Each test verifies a certain “feature” • But that’s all right: we’ve started designing the
interface by using it

Red Bar Green Bar


• A test can be defined before the code is written • What’s the simplest way to make a test pass?
SmallSet SmallSet
class SmallSet { class SmallSet {
public int size() { public int size() {
return 42; return 0;
} }
} }

• “Fake it till you make it”


• Running the test
yields a red bar • Re-running yields the legendary JUnit Green
indicating failure: Bar:

• If we add the size function and re-run the the


test, it works! • Move on with the next feature

Adding Items Adding Items


• To implement adding items, we first test for it: • The test now fails as expected:
SmallSetTest • It seems obvious we need to count the number
class SmallSetTest { of items: SmallSet
@Test public void testEmptySetSize() ...
private int _size = 0;
@Test public void testAddOne() {
SmallSet s = new SmallSet(); public int size() {
s.add(new Object()); return 0;
return _size;
assertEquals(1, s.size());
} }
}
public void add(Object o) {
• add() is undefined, so to run the test we ++_size;
}
define it: SmallSet • And we get a green bar:
public int size() ...

public void add(Object o) {}

3
13/02/2012

Adding Something Again Remember that Item?...


• So what if we added an item already in the set? • We need to remember which items are in the
SmallSetTest set... SmallSet
class SmallSetTest { private int _size = 0;
@Test public void testEmptySetSize() ... public static final int MAX = 10;
private Object _items[] = new Object[MAX];
@Test public void testAddOne() ... ...
public void add(Object o) {
@Test public void testAddAlreadyInSet() { for (int i=0; i < MAX; i++) {
SmallSet s = new SmallSet(); if (_items[i] == o) {
Object o = new Object(); return;
s.add(o); }
s.add(o); }
assertEquals(1, s.size()); _items[_size] = o;
} ++_size;
} }

• As expected, the test fails... • All tests pass, so we can refactor that loop...

Refactoring Too Many


• FOR-loop doesn’t “speak to us” as it could... • What if we try to add more than SmallSet can hold?
SmallSet (before) SmallSet (after) SmallSetTest
public void add(Object o) { private boolean inSet(Object o) { ...
for (int i=0; i < MAX; i++) { for (int i=0; i < MAX; i++) { @Test public void testAddTooMany() {
if (_items[i] == o) { if (_items[i] == o) { SmallSet s = new SmallSet();
return; return true; for (int i=0; i < SmallSet.MAX; i++) {
} } s.add(new Object());
} } }
_items[_size] = o; return false; s.add(new Object());
++_size; } }
}
public void add(Object o) {
if (!inSet(o)) {
_items[_size] = o;
++_size;
• The test fails with an error:
}
ArrayIndexOutOfBoundsException
}
• We know why this occurred, but it should bother
• All tests still pass, so we didn’t break it! us: “ArrayIndex” isn’t a sensible error for a “set”

Size Matters Testing for Exceptions


• We first have add() check the size, • ... finally test for our exception:
SmallSet SmallSetTest
public void add(Object o) { @Test public void testAddTooMany() {
if (!inSet(o) && _size < MAX) { SmallSet s = new SmallSet();
_items[_size] = o; for (int i=0; i < SmallSet.MAX; i++) {
++_size; s.add(new Object());
} }
} try {
s.add(new Object());
• ... re-run the tests, check for green, fail(“SmallSetFullException expected”);
}
define our own exception... catch (SmallSetFullException e) {}
}
SmallSetFullException
public class SmallSetFullException extends Error {} • The test fails as expected,
• ... re-run the tests, check for green, so now we fix it...
and...

4
13/02/2012

Testing for Exceptions After all Tests are Passed


• Is the code is correct?
• ... so now we modify add() to throw: – Yes, if we wrote the right tests.
SmallSet • Is the code efficient?
public void add(Object o) { – Probably used simplest solution first.
if (!inSet(o) && _size < MAX) { – Replace simple data structures with better data structures.
if (_size >= MAX) { – New ideas on how to compute the same while doing less work.
throw new SmallSetFullException(); • Is the code readable, elegant, and easy to maintain?
} – It is very common to find some chunk of working code, make a replica, and
_items[_size] = o; then edit the replica.
++_size; – But this makes your software fragile
} • Later changes have to be done on all instances, or
} • some become inconsistent
– Duplication can arise in many ways:
• constants (repeated “magic numbers”)
• code vs. comment
• All tests now pass, so we’re done: •

within an object’s state
...

“DRY” Principle Simple Refactoring


• Don’t Repeat Yourself • Renaming variables, methods, classes for readability.
• Explicitly defining constants:
• A nice goal is to have each piece of public double weight(double mass) { static final double GRAVITY = 9.80665;
return mass * 9.80665;
knowledge live in one place } public double weight(double mass) {
return mass * GRAVITY;
• But don’t go crazy over it }

– DRYing up at any cost can increase dependencies – If your application later gets used as part of a Nasa mission
between code to Mars, it won’t make mistakes
– Every place that the gravitational constant shows up in your
– “3 strikes and you refactor” (i.e., clean up) program a reader will realize that this is what she is looking
at
– The compiler may actually produce better code

Extract Method Extract Method


• A comment explaining what is being done • Simplifying conditionals with Extract
usually indicates the need to extract a method Method
public double totalArea() { public double totalArea() { before
... ... if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
// now add the circle area += circleArea(radius); charge = quantity * _winterRate + _winterServiceCharge;
area += PI * pow(radius,2); ... }
... } else {
} charge = quantity * _summerRate;
private double circleArea(double radius) { }
return PI * pow(radius, 2);
}
after
if (isSummer(date)) {
charge = summerCharge(quantity);
• One of the most common refactorings }
else {
charge = winterCharge(quantity);
}

5
13/02/2012

Review Is testing obligatory?


• Started with a “to do” list of tests / features • When you write code in professional settings
• could have been expanded with teammates, definitely!
as we thought of more tests / features – In such settings, failing to test your code just means
• Added features in small iterations you are inflicting errors you could have caught on
teammates!
– People get fired for this sort of thing!
– So… in industry… test or perish!
• But what if code is just “for yourself”?
– Testing can still help you debug, and if you go to the
trouble of doing the test, JUnit helps you “keep it” for
• “a feature without a test doesn’t exist” re-use later.
– “I have never written a program that was correct
before I tested and debugged it.” Prof. Joachims

Fixing a Bug A bug can reveal a missing test


• What if after releasing we found a bug? • … but can also reveal that the specification was
faulty in the first place, or incomplete
– Code “evolves” and some changing conditions can
trigger buggy behavior
– This isn’t your fault or the client’s fault but finger
pointing is common
• Great testing dramatically reduces bug rates
– And can make fixing bugs way easier
– But can’t solve everything: Paradise isn’t attainable in
the software industry
Famous last words: “It works!”

Reasons for TDD Not the Whole Story


• By writing the tests first, we • There’s a lot more worth knowing about TDD
• test the tests • What to test / not to test
• design the interface by using it » e.g.: external libraries?
• ensure the code is testable • How to refactor tests
• ensure good test coverage
• Fixtures
• By looking for the simplest way to make • Mock Objects
tests pass, • Crash Test Dummies
• the code becomes “as simple as possible, but no • ...
simpler”
Beck, Kent: Test-Driven Development: By Example
• may be simpler than you thought!

6
13/02/2012

How people write really big programs Testing is a part of that style!
• When applications are small, you can understand • Once you no longer know how big parts of the
every element of the system system even work (or if they work), you instead
begin to think in terms of
• But as systems get very large and complex, you – Code you’ve written yourself. You tested it and know
that it works!
increasingly need to think in terms of interfaces,
documentation that defines how modules work, – Modules you make use of. You wrote experiments to
confirm that they work the way you need them to
and your code is more fragmented work
– Tests of the entire complete system, to detect issues
• This forces you into a more experimental style visible only when the whole thing is running or only
under heavy load

Junit testing isn’t enough The Q/A cycle


• For example, many systems suffer from “leaks” • Real companies have quality assurance teams
– Such as adding more and more objects to an ArrayList
– The amount of memory just grows and grows
• They take the code and refuse to listen to all the
• Some systems have issues triggered only in big long-winded explanations of why it works
deployments, like cloud computing settings
• Then they do their own, independent, testing
• Sometimes the application “specification” was flawed, and
a correct implementation of the specification will look
erroneous to the end user • And then they send back the broken code with a long
list of known bugs!
• But a thorough test plan can reveal all such problems
• Separating development from Q/A really helps

Why is Q/A a cycle? Even with fantastic Q/A…


• Each new revision may fix bugs but could also • The best code written by professionals will still have
break things that were previously working some rate of bugs
– They reflect design oversights, or bugs that Q/A somehow
didn’t catch
• Moreover, during the lifetime of a complex – Evolutionary change in requirements
application, new features will often be added and – Incompatibilities between modules developed by different
those can also require Q/A people, or enhancements made by people who didn’t fully
understand the original logic

• Thus companies think of software as having a very • So never believe that software will be flawless
long “life cycle”. Developing the first version is
only the beginning of a long road! • Our goal in cs2110 is to do as well as possible
• In later CS courses we’ll study “fault tolerance”!

You might also like