Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
115-214
SchoolofComputerScience
PrinciplesofSoftwareConstruction:Classinvariants,immutability,andtesting
JoshBloch CharlieGarrod
215-214
Administrivia
• Homework4aduetoday,11:59p.m.• Designreviewmeetingismandatory– Butweexpectittobereallyhelpful– Feedbackisawonderfulthing
• PSA– Youhavelessthanoneweeklefttoregistertovote!Dealine isOctober11!
315-214
KeyconceptsfromTuesday…
• Internalrepresentationsmatter– Thewrongrepresentationcanbetoxic
• Codemustbecleanandconcise– Repetitionistoxic
• Goodcodinghabitsmatter
415-214
Outline
• Classinvariantsanddefensivecopying• Immutability• Testingandcoverage• Testingforcomplexenvironments• Implementationtestingwithassertions
515-214
Classinvariants
• Criticalpropertiesofthefieldsofanobject• Establishedbytheconstructor• Maintainedbypublicmethodinvocations–Maybeinvalidatedtemporarilyduringmethodexecution
615-214
Safelanguagesandrobustprograms
• UnlikeC/C++,Javalanguagesafe– Immunetobufferoverruns,wildpointers,etc.
• Makesitpossibletowriterobust classes– Correctnessdoesn’tdependonothermodules– Eveninsafelanguage,requiresprogrammereffort
715-214
Defensiveprogramming
• Assumeclientswilltrytodestroyinvariants– Mayactuallybetrue(malicioushackers)– Morelikely:honestmistakes
• Ensureclassinvariantssurviveanyinputs– Defensivecopying–Minimizingmutability
815-214
public final class Period {private final Date start, end; // Invariant: start <= end
/*** @throws IllegalArgumentException if start > end* @throws NullPointerException if start or end is null*/
public Period(Date start, Date end) {if (start.after(end))
throw new IllegalArgumentException(start + " > " + end);this.start = start;this.end = end;
}
public Date start() { return start; }public Date end() { return end; }... // Remainder omitted
}
Thisclassisnot robust
915-214
Theproblem:Date ismutable// Attack the internals of a Period instanceDate start = new Date(); // (The current time)Date end = new Date(); // " " "Period p = new Period(start, end);end.setYear(78); // Modifies internals of p!
1015-214
Thesolution:defensivecopying// Repaired constructor - defensively copies parameterspublic Period(Date start, Date end) {
this.start = new Date(start.getTime());this.end = new Date(end.getTime());if (this.start.after(this.end))throw new IllegalArgumentException(start +“ > "+ end);
}
1115-214
Afewimportantdetails• Copiesmadebefore checkingparameters• Validitycheckperformedoncopies• Eliminateswindowofvulnerabilitybetweenparametercheckandcopy
• ThwartsmultithreadedTOCTOUattack– Time-Of-Check-To-Time-Of-U
// BROKEN - Permits multithreaded attack!public Period(Date start, Date end) {
if (start.after(end))throw new IllegalArgumentException(start + " > " + end);
// Window of vulnerabilitythis.start = new Date(start.getTime());this.end = new Date(end.getTime());
}
1215-214
Anotherimportantdetail• Usedconstructor,notclone,tomakecopies– Necessarybecause Date classisnonfinal– Attackercouldimplementmalicioussubclass• Recordsreferencetoeachextantinstance• Providesattackerwithaccesstoinstancelist
• Butwhousesclone,anyway?[EJItem11]
1315-214
Unfortunately,constructorsareonlyhalfthebattle
// Accessor attack on internals of PeriodPeriod p = new Period(new Date(), new Date());Date d = p.end();p.end.setYear(78); // Modifies internals of p!
1415-214
Thesolution:moredefensivecopying
// Repaired accessors - defensively copy fieldspublic Date start() {
return new Date(start.getTime());}public Date end() {
return new Date(end.getTime());}
NowPeriodclassisrobust!
1515-214
Summary• Don’tincorporatemutableparametersintoobject;makedefensivecopies
• Returndefensivecopiesofmutablefields…• Orreturnunmodifiable viewofmutablefields• Reallesson– useimmutablecomponents– Eliminatestheneedfordefensivecopying
1615-214
Outline
• Classinvariantsanddefensivecopying• Immutability• Testingandcoverage• Testingforcomplexenvironments• Implementationtestingwithassertions
1715-214
Immutableclasses
• Classwhoseinstancescannotbemodified• Examples:String,Integer,BigInteger• How,why,andwhentousethem
1815-214
Howtowriteanimmutableclass
• Don’tprovideanymutators• Ensurethatnomethodsmaybeoverridden• Makeallfieldsfinal• Makeallfieldsprivate• Ensuresecurityofanymutablecomponents
1915-214
public final class Complex {private final double re, im;
public Complex(double re, double im) {this.re = re;this.im = im;
}
// Getters without corresponding setterspublic double realPart() { return re; }public double imaginaryPart() { return im; }
// subtract, multiply, divide similar to addpublic Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);}
Immutableclassexample
2015-214
@Override public boolean equals(Object o) {if (!(o instanceof Complex)) return false;Complex c = (Complex)o;return Double.compare(re, c.re) == 0 &&
Double.compare(im, c.im) == 0;}
@Override public int hashCode() {return 31*Double.hashCode(re) + Double.hashCode(im);
}
@Override public String toString() {return String.format("%d + %di”, re, im)";
}}
Immutableclassexample(cont.)Nothinginterestinghere
2115-214
Distinguishingcharacteristic
• Returnnewinstanceinsteadofmodifying• Functionalprogramming• Mayseemunnaturalatfirst• Manyadvantages
2215-214
Advantages
• Simplicity• InherentlyThread-Safe• Canbesharedfreely• Noneedfordefensivecopies• Excellentbuildingblocks
2315-214
Majordisadvantage
• Separateinstanceforeachdistinctvalue• Creatingtheseinstancescanbecostly
BigInteger moby = ...; // A million bits longmoby = moby.flipBit(0); // Ouch!
• Problemmagnifiedformultistepoperations–Well-designedimmutableclassesprovidecommonmultistepoperationsasprimitives
– Alternative:mutablecompanionclass• e.g.,StringBuilder forString
2415-214
Whentomakeclassesimmutable
• Always,unlessthere'sagoodreasonnotto• Alwaysmakesmall“valueclasses”immutable!– Examples:Color,PhoneNumber,Unit– Date andPoint weremistakes!– Expertsoftenuselong insteadofDate
2515-214
Whentomakeclassesmutable
• Classrepresentsentitywhosestatechanges– Real-world- BankAccount,TrafficLight– Abstract- Iterator, Matcher, Collection– Processclasses- Thread,Timer
• Ifclassmustbemutable,minimizemutability– Constructorsshouldfullyinitializeinstance– Avoidreinitializemethods
2615-214
Outline
• ClassInvariants• Immutability• Testingandcoverage• Testingforcomplexenvironments• Implementationtestingwithassertions
2715-214
Whydowetest?
2815-214
Testingdecisions
• Whotests?– Developerswhowrotethecode– QualityAssuranceTeamandTechnicalWriters– Customers
• Whentotest?– Beforeandduringdevelopment– Aftermilestones– Beforeshipping– Aftershipping
2915-214
Testdrivendevelopment
• Writetestsbeforecode• Neverwritecodewithoutafailingtest• Codeuntilthefailingtestpasses
3015-214
Whyusetestdrivendevelopment?
• Forcesyoutothinkaboutinterfacesearly• Higherproductquality– Bettercodewithfewerdefects
• Highertestsuitequality• Higherproductivity• It’sfuntowatchtestspass
3115-214
TDDinpractice
• EmpiricalstudiesonTDDshow:–Mayrequiremoreeffort–Mayimprovequalityandsavetime
• SelectiveuseofTDDisbest• AlwaysuseTDDforbugreports– Regressiontests
3215-214
Howmuchtesting?
• Yougenerallycannottestallinputs– Toomany– usuallyinfinite
• Butwhenitworks,exhaustivetestingisbest!
3315-214
Whatmakesagoodtestsuite?
• Provideshighconfidencethatcodeiscorrect• Short,clear,andnon-repetitious–Moredifficultfortestsuitesthanregularcode– Realistically,testsuiteswilllookworse
• Canbefuntowriteifapproachedinthisspirit
3415-214
Nextbestthingtoexhaustivetesting:randominputs
• Alsoknowasfuzztesting,torturetesting• Try“random”inputs,asmanyasyoucan– Chooseinputstotickleinterestingcases– Knowledgeofimplementationhelpshere
• Seedrandomnumbergeneratorsotestsrepeatable
3515-214
Black-boxtesting
• Lookatspecifications,notcode• Testrepresentativecases• Testboundaryconditions• Testinvalid(exception)cases• Don’ttestunspecifiedcases
3615-214
White-boxtesting
• Lookatspecificationsand code• Writeteststo:– Checkinterestingimplementationcases–Maximizebranchcoverage
3715-214
Codecoveragemetrics
• Methodcoverage– coarse• Branchcoverage– fine• Pathcoverage– toofine– Costishigh,valueislow– (Relatedtocyclomatic complexity)
3815-214
Coveragemetrics:usefulbutdangerous
• Cangivefalsesenseofsecurity• Examplesofwhatcoverageanalysiscouldmiss– Datavalues– Concurrencyissues– raceconditionsetc.– Usabilityproblems– Customerrequirementsissues
• Highbranchcoverageisnot sufficient
3915-214
Testsuites– idealandreal
• Idealtestsuites– Uncoverallerrorsincode– Test“non-functional”attributessuchasperformanceandsecurity
–Minimumsizeandcomplexity• RealtestSuites– Uncoversomeportionoferrorsincode– Haveerrorsoftheirown– Arenonethelesspriceless
4015-214
Outline
• Classinvariants• Immutability• Testingandcoverage• Testingforcomplexenvironments• Implementationtestingwithassertions
4115-214
Problemswhentestingsomeapps
• User-facingapplications– Usersclick,drag,etc.,andinterpretoutput– Timingissues
• Testingagainstbiginfrastructure– Databases,webservices,etc.
• Realworldeffects– Printing,mailingdocuments,etc.
• Collectivelycomprisethetestenvironment
4215-214
Example– Tiramisuapp
• Mobilerouteplanningapp• AndroidUI• BackenduseslivePATdata
4315-214
Anotherexample
• 3rdpartyFacebook apps• Androiduserinterface• BackendusesFacebookdata
4415-214
TestinginrealenvironmentsCode FacebookAndroidclient
void buttonClicked() {render(getFriends());
}List<Friend> getFriends() {
Connection c = http.getConnection();FacebookApi api = new Facebook(c);List<Node> persons = api.getFriends("john");for (Node person1 : persons) {
for (Node person2 : persons) {…}
}return result;
}
4515-214
EliminatingAndroiddependencyCode FacebookTestdriver
@Test void testGetFriends() {assert getFriends() == ...;
} List<Friend> getFriends() {
Connection c = http.getConnection();FacebookAPI api = new FacebookAPI(c);List<Node> persons = api.getFriends("john");for (Node person1 : persons) {
for (Node person2 : persons) {…}
}return result;
}
4615-214
Thatwon’tquitework
• GUIapplicationsprocessthousands ofevents• Solution:automatedGUItestingframeworks– AllowstreamsofGUIeventstobecaptured,replayed
• Thesetoolsaresometimescalledrobots
4715-214
EliminatingFacebook dependencyCode Mock
@Test void testGetFriends() {assert getFriends() == …;
} List<Friend> getFriends() {
FacebookApi api = new MockFacebook(c);List<Node> persons = api.getFriends("john");for (Node person1 : persons) {
for (Node person2 : persons) {…}
}return result;
}
Testdriver
4815-214
Thatwon’tquitework!
• Changingproductioncodefortestingunacceptable• Problemcausedbyconstructor incode• Usefactoryinsteadofconstructor• Usetoolstofacilitatethissortoftesting– Dependencyinjectiontools,e.g.,Dagger,Guice–MockobjectframeworkssuchasMockito
4915-214
Faultinjection
• Mockscanemulatefailuressuchastimeouts• Allowsyoutoverifytherobustnessofsystem
Code MockFacebookTestdriver
5015-214
Advantagesofusingmocks
• Testcodelocallywithoutlargeenvironment• Enabledeterministictests• Enablefaultinjection• Canspeeduptestexecution– e.g.,avoidslowdatabaseaccess
• Cansimulatefunctionalitynotyetimplemented• Enabletestautomation
5115-214
DesignImplications
• Thinkabouttestabilitywhenwritingcode• Whenamockmaybeappropriate,designforit• Hidesubsystemsbehindaninterfaces• Usefactories,notconstructorstoinstantiate• Useappropriatetools– Dependencyinjectionormockingframeworks
5215-214
MoreTestingin15-313FoundationsofSoftwareEngineering• Manualtesting• Securitytesting,penetrationtesting• Fuzztestingforreliability• Usabilitytesting• GUI/Webtesting• Regressiontesting• Differentialtesting• Stress/soaktesting
5315-214
Outline
• ClassInvariants• Immutability• Testsuitesandcoverage• Testingforcomplexenvironments• Implementation-testingwithassertions
5415-21454
Whatisanassertion?
• Statementcontainingboolean expressionthatprogrammerbelievestobetrue:
assert speed <= SPEED_OF_LIGHT;
• Evaluatedatruntime– throwsError iffalse• Disabledbydefault- noperformanceeffect• Typicallyenabledduringdevelopment• Canenableinthefieldwhenproblemsoccur!
5515-214
Syntax
AssertStatement:assert Expression1 ;assert(Expression1, Expression2) ;
• Expression1 - assertedcondition(boolean)• Expression2 - detailmessageof AssertionError
55
5615-214
Whyuseassertions?
• Document&testprogrammer'sassumptions– e.g.,classinvariants
• Verifyprogrammer’sunderstanding• Quicklyuncoverbugs• Increaseconfidencethatprogramisbug-free• Assertsturnblackboxtestsintowhiteboxtests
56
5715-21457
Lookfor“assertivecomments”int remainder = i % 3;if (remainder == 0) {
...} else if (remainder == 1) {
...} else { // (remainder == 2)
...}
5815-21458
Replacewithrealassertions!int remainder = i % 3;if (remainder == 0) {
...} else if (remainder == 1) {
...} else {
assert remainder == 2;...
}
5915-21459
Usesecondargumentforfailurecapture
if (i % 3 == 0) {...
} else if (i % 3 == 1) {...
} else {assert (i % 3 == 2, i);...
}
6015-21460
Lookforswitchwithnodefaultswitch(flavor) {
case VANILLA:...break;
case CHOCOLATE:...break;
case STRAWBERRY:...
}
6115-21461
Addan“assertivedefault”switch(flavor) {case VANILLA:...break;
case CHOCOLATE:...break;
case STRAWBERRY:...break;
default:assert (false, flavor);
}
6215-21462
Donotuseassertionsforpublic preconditions
/*** Sets the refresh rate.** @param rate refresh rate, in frames per second.* @throws IllegalArgumentException if rate <= 0* or rate > MAX_REFRESH_RATE.*/public void setRefreshRate(int rate) {
if (rate <= 0 || rate > MAX_REFRESH_RATE)throw new IllegalArgumentException(...);
setRefreshInterval(1000 / rate);}
6315-21463
Do useassertionsfornon-public preconditions
/*** Sets the refresh interval (which must correspond* to a legal frame rate).** @param interval refresh interval in ms*/private void setRefreshInterval(int interval) {
assert interval > 0 && interval <= 1000, interval;... // Set the refresh interval
}
6415-21464
Do useassertionsforpostconditions/*** Returns BigInteger whose value is (this-1 mod m).* @throws ArithmeticException if m <= 0, or this* BigInteger is not relatively prime to m.*/public BigInteger modInverse(BigInteger m) {
if (m.signum() <= 0)throw new ArithmeticException(m + "<= 0");
... // Do the computationassert this.multiply(result).mod(m).equals(ONE);return result;
}
6515-21465
Complexpostconditions
void foo(int[] a) {// Manipulate contents of array...
// Array will appear unchanged}
6615-21466
Assertionsforcomplexpostconditions
void foo(final int[] a) {class DataCopy {
private int[] aCopy;DataCopy() { aCopy = (int[]) a.clone(); }boolean isConsistent() {
return Arrays.equals(a, aCopy);}
}DataCopy copy = null;assert (copy = new DataCopy()) != null;... // Manipulate contents of arrayassert copy.isConsistent();
}
6715-21467
Caveat– assertsmustnothavesideeffectsvisibleoutsideotherasserts
Dothis:boolean modified = set.remove(elt);assert modified;
Notthis:assert set.remove(elt); //Bug!
6815-21468
Sermon:acceptassertionsintoyourlife
• Programmer’sinteriormonologue:– “Nowatthispoint,weknow...”
• During,notafter,development• Quicklybecomessecondnature• Paysbigcode-qualitydividends
6915-214
Conclusion
• Tomaintainclassinvariants–Minimizemutability–Makedefensivecopieswhererequired
• Interfacetestingiscritical– Designinterfacestofacilitatetesting– Coveragetoolscanhelpgaugetestsuitequality
• Useassertionstotestimplementationdetails– Assertsamplifythevalueofyourinterfacetests