Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
115-214
SchoolofComputerScience
PrinciplesofSoftwareConstruction:HowtoDesignaGoodAPIandWhyitMatters
JoshBloch CharlieGarrod
215-214
Administrivia
• Homework4bduenextThursday• HW4afeedbackavailableafterclass
315-214
KeyconceptsfromTuesday…
• Multiplethreadsruninsameprogramconcurrently– Canimprovestyleandperformance– Butyoumustavoidsharedmutablestate…– Orsynchronizeproperly
• GUIprogrammingasimplecaseofmultithreading– EventdispatchthreadhandlesallGUIevents– Swingcallsmustbeon EDT– Time-consuming(non-Swing)mustbeoffEDT
415-214
Today’stopicAPIDesignWhatisanAPI?• ShortforApplicationProgrammingInterface• Acomponentspecificationintermsofitsoperations,theirinputs,andtheiroutputs– Definesasetoffunctionalitiesindependentofimplementation– Allowsimplementationtovarywithoutcompromisingclients
• Definesboundarybetweencomponentsinaprogrammaticsystem– Intermodular boundary
• Apublic APIisonedesignedforusebyothers
515-214
Libraries,frameworksbothdefineAPIs
Library
Framework
public MyWidget extends JContainer {
ublic MyWidget(int param) {/ setup internals, without rendering}
/ render component on first view and resizingprotected void paintComponent(Graphics g) {// draw a red box on his componentDimension d = getSize();g.setColor(Color.red);g.drawRect(0, 0, d.getWidth(), d.getHeight()); }}
public MyWidget extends JContainer {
ublic MyWidget(int param) {/ setup internals, without rendering}
/ render component on first view and resizingprotected void paintComponent(Graphics g) {// draw a red box on his componentDimension d = getSize();g.setColor(Color.red);g.drawRect(0, 0, d.getWidth(), d.getHeight()); }}
your code
your code
API
API
615-214
WhyisAPIdesignimportant?
• APIscanbeamongyourgreatestassets– Usersinvestheavily:acquiring,writing,learning– Costtostop usinganAPIcanbeprohibitive– SuccessfulpublicAPIscaptureusers
• Canalsobeamongyourgreatestliabilities– BadAPIcancauseunendingstreamofsupportcalls– Caninhibitabilitytomoveforward
• PublicAPIsareforever– onechancetogetitright
715-214
WhyisAPIdesignimportanttoyou?
• Ifyouprogram,youareanAPIdesigner– Goodcodeismodular– eachmodulehasanAPI
• Usefulmodulestendtogetreused– Goodreusablemodulesareanasset– Oncemodulehasusers,can’tchangeAPIatwill
• ThinkingintermsofAPIsimprovescodequality
815-214
CharacteristicsofagoodAPI
• Easytolearn• Easytouse,evenwithoutdocumentation• Hardtomisuse• Easytoreadandmaintaincodethatusesit• Sufficientlypowerfultosatisfyrequirements• Easytoevolve• Appropriatetoaudience
915-214
Outline
• TheProcessofAPIDesign• GeneralPrinciples• ClassDesign• MethodDesign• ExceptionDesign
1015-214
Gatherrequirements–skeptically
• Oftenyou'llgetproposedsolutionsinstead– Bettersolutionsmayexist
• Yourjobistoextracttruerequirements– Shouldtaketheformofuse-cases
• Canbeeasier&morerewardingtobuildmoregeneralAPI
Whattheysay:“WeneednewdatastructuresandRPCswiththeVersion2attributes”
Whattheymean:“Weneedanewdataformatthataccommodatesevolutionofattributes”
1115-214
Startwithshortspec– 1pageisideal
• Atthisstage,agilitytrumpscompleteness• Bouncespecoffasmanypeopleaspossible– Listentotheirinputandtakeitseriously
• Ifyoukeepthespecshort,it’seasytomodify• Fleshitoutasyougainconfidence
1215-214
SampleearlyAPIdraft// A collection of elements (root of the collection hierarchy)public interface Collection<E> {
// Ensures that collection contains oboolean add(E o);
// Removes an instance of o from collection, if presentboolean remove(Object o);
// Returns true iff collection contains oboolean contains(Object o) ;
// Returns number of elements in collectionint size() ;
// Returns true if collection is emptyboolean isEmpty();
... // Remainder omitted}
1315-214
WritetoyourAPIearlyandoften
• Startbefore you'veimplementedtheAPI– Savesyoudoingimplementationyou'llthrowaway
• Startbefore you'veevenspecifieditproperly– Savesyoufromwritingspecsyou'llthrowaway
• ContinuewritingtoAPIasyoufleshitout– Preventsnastysurprisesrightbeforeyouship
• Codelivesonasexamples,unittests– Amongthemostimportantcodeyou’lleverwrite– FormsthebasisofDesignFragments[Fairbanks,Garlan,&Scherlis,OOPSLA‘06,P.75]
1415-214
Write3implementationsofeachabstractclassorinterfacebeforerelease• Ifyouwrite1,itprobablywon’tsupportanother• Ifyouwrite2,itwillsupportmorewithdifficulty• Ifyouwrite3,itwillworkfine• WillTraczcallsthis“TheRuleofThrees• ConfessionsofaUsedProgramSalesman,Addison-Wesley,1995)
1515-214
Maintainrealisticexpectations
• MostAPIdesignsareover-constrained– Youwon'tbeabletopleaseeveryone– Aimtodispleaseeveryoneequally
• Expecttomakemistakes– Afewyearsofreal-worldusewillflushthemout– ExpecttoevolveAPI
1615-214
Outline
• TheProcessofAPIDesign• GeneralPrinciples• ClassDesign• MethodDesign• ExceptionDesign
1715-214
APIshoulddoonethinganddoitwell
• Functionalityshouldbeeasytoexplain– Ifit'shardtoname,that'sgenerallyabadsign– Goodnamesdrivedevelopment– Beamenabletosplittingandmergingmodules
• Good:Font,Set,PrivateKey,Lock,ThreadFactory,TimeUnit,Future<T>
• Bad:DynAnyFactoryOperations,_BindingIteratorImplBase,ENCODING_CDR_ENCAPS,OMGVMCID
1815-214
APIshouldbeassmallaspossiblebutnosmaller• APIshouldsatisfyitsrequirements• Whenindoubtleaveitout– Functionality,classes,methods,parameters,etc.– Youcanalwaysadd,butyoucanneverremove
• Conceptualweightmoreimportantthanbulk• Lookforagoodpower-to-weightratio
1915-214
ImplementationshouldnotimpactAPI
• ImplementationdetailsinAPIs areharmful– Confuseusers– Inhibitfreedomtochangeimplementation
• Beawareofwhatisanimplementationdetail– Donotoverspecify thebehaviorofmethods• Forexample:donotspecifyhashfunctions
– Alltuningparameters aresuspect• Don'tletimplementationdetails“leak”intoAPI– Serializedforms,exceptionsthrown
2015-214
Minimizeaccessibilityofeverything
• Makeclasses,membersasprivateaspossible• Publicclassesshouldhavenopublicfields(withtheexceptionofconstants)
• Maximizesinformationhiding [Parnas]• Minimizescoupling– Allowsmodulestobe,understood,used,built,tested,debugged,andoptimizedindependently
2115-214
Namesmatter– APIisalittlelanguage
• NamesShouldBeLargelySelf-Explanatory– Avoidcrypticabbreviations
• Beconsistent– SamewordmeanssamethingthroughoutAPI– (andideally,acrossAPIsontheplatform)
• Beregular– striveforsymmetry• Ifyougetitright,codereadslikeprose
for (List<Integer> proposedSolution : Permutations.of(digits))if (isSolution(proposedSolution))
solutions.add(proposedSolution);
2215-214
Grammarisapartofnaming
• Nounsforclasses– BigInteger,PriorityQueue
• Nounsoradjectivesforinterfaces– Collection,Comparable
• Nouns,linkingverbsorprepositionsfornon-mutativemethods– size,isEmpty,plus
• Actionverbsformutativemethods– put,add,clear
2315-214
Documentationmatters
Reuseissomethingthatisfareasiertosaythantodo.Doingitrequiresbothgooddesignandverygooddocumentation.Evenwhenweseegooddesign,whichisstillinfrequently,wewon'tseethecomponentsreusedwithoutgooddocumentation.
– D.L.Parnas,SoftwareAging.Proceedingsofthe16thInternationalConferenceonSoftwareEngineering,1994
2415-214
Documentreligiously
• Documentevery class,interface,method,constructor,parameter,andexception– Class:whataninstancerepresents–Method:contractbetweenmethodanditsclient• Preconditions,postconditions,side-effects
– Parameter:indicateunits,form,ownership• Documentthreadsafety• Ifclassismutable,documentstatespace
2515-214
Considerperformanceconsequences
• Baddecisionscanlimitperformance–Makingtypemutable– Providingconstructorinsteadofstaticfactory– Usingimplementationtypeinsteadofinterface
• DonotwarpAPItogainperformance– Underlyingperformanceissuewillgetfixed,butheadacheswillbewithyouforever
– Gooddesignusuallycoincideswithgoodperformance
2615-214
PerformanceeffectsofabadAPIdecisionscanberealandpermanent• Component.getSize() returnsDimension• Dimension ismutable• EachgetSize callmustallocateDimension • Causesmillionsofneedlessobjectallocations• Alternativeaddedin1.2;oldclientcodestillslow– getX(),getY()
2715-214
APImustcoexistpeacefullywithplatform
• Dowhatiscustomary– Obeystandardnamingconventions– Avoidobsoleteparameterandreturntypes– MimicpatternsincoreAPIsandlanguage
• TakeadvantageofAPI-friendlyfeatures– Generics,varargs,enums,functionalinterfaces
• KnowandavoidAPItrapsandpitfalls– Finalizers,publicstaticfinalarrays,etc.
• Don’ttransliterateAPIs
2815-214
Outline
• TheProcessofAPIDesign• GeneralPrinciples• ClassDesign• MethodDesign• ExceptionDesign
2915-214
Minimizemutability
• Classesshouldbeimmutableunlessthere’sagoodreasontodootherwise– Advantages:simple,thread-safe,reusable– Disadvantage:separateobjectforeachvalue
• Ifmutable,keepstate-spacesmall,well-defined– Makeclearwhenit'slegaltocallwhichmethod
Bad:Date,CalendarGood:TimerTask
3015-214
Subclassonlywhereitmakessense
• Subclassing impliessubstitutability(Liskov)– Don’tsubclassunlessanis-arelationshipexists– Otherwise,usecomposition
• Don’tsubclassjusttoreuseimplementation
Bad:Properties extendsHashtableStack extendsVector
Good:Set extendsCollection
3115-214
Design&documentforinheritanceorelseprohibitit• Inheritanceviolatesencapsulation(Snyder,‘86)– Subclassesaresensitivetoimplementationdetailsofsuperclass
• Ifyouallowsubclassing,documentself-use– Howdomethodsuseoneanother?
• Conservativepolicy:allconcreteclassesfinal
Bad:ManyconcreteclassesinJ2SElibrariesGood:AbstractSet,AbstractMap
3215-214
Outline
• TheProcessofAPIDesign• GeneralPrinciples• ClassDesign• MethodDesign• ExceptionDesign
3315-214
Don'tmaketheclientdoanythingthemodulecoulddo• Reduceneedforboilerplatecode– Generallydoneviacut-and-paste– Ugly,annoying,anderror-prone
import org.w3c.dom.*;import java.io.*;import javax.xml.transform.*;import javax.xml.transform.dom.*;import javax.xml.transform.stream.*;
/** DOM code to write an XML document to a specified output stream. */static final void writeDoc(Document doc, OutputStream out)throws IOException{
try {Transformer t = TransformerFactory.newInstance().newTransformer();t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());t.transform(new DOMSource(doc), new StreamResult(out)); // Does actual writing
} catch(TransformerException e) {throw new AssertionError(e); // Can’t happen!
}}
3415-214
Don’tviolatetheprincipleofleastastonishment
• UserofAPIshouldnotbesurprisedbybehavior– It'sworthextraimplementationeffort– It'sevenworthreducedperformance
public class Thread implements Runnable {// Tests whether current thread has been interrupted.// Clears the interrupted status of current thread.public static boolean interrupted();
}
3515-214
Here’swhathappensifyouviolatebothofthelasttworulesatthesametime// Spec says: "Skips over n bytes of data from this input."// But this is a a lie. Ignore return value at your peril!public long skip(long n) throws IOException;
Theonly correctwaytouseInputStream.skip:static void skipFully(InputStream in, long nBytes)
throws IOException {long remaining = nBytes;while (remaining != 0) {
long skipped = in.skip(remaining);if (skipped == 0) // EOF
throw new EOFException();remaining -= skipped;
} }
3615-214
APIsshouldfailfast:reporterrorsassoonaspossible• Compiletimeisbest– statictyping,generics
public int max(int... args);public int max(int first, int... rest);
• Atruntime,firstbadmethodinvocationisbest–Methodshouldbefailure-atomic
/** A Properties instance maps strings to strings */public class Properties extends Hashtable {
public Object put(Object key, Object value);
// Throws ClassCastException if this properties// contains any keys or values that are not stringspublic void save(OutputStream out, String comments);
}
3715-214
Provideprogrammaticaccesstoalldataavailableinstringform• Otherwise,clientswillparsestrings– Painfulforclients–Worse,turnsstringformatintodefactoAPI
public class Throwable {public void printStackTrace(PrintStream s);public StackTraceElement[] getStackTrace(); // Since 1.4
}public final class StackTraceElement {public String getFileName();public int getLineNumber();public String getClassName();public String getMethodName();public boolean isNativeMethod();
}
3815-214
Overloadwithcare
• Avoidambiguous overloadings–Multipleoverloadings applicabletosameactuals
• Justbecauseyoucandoesn'tmeanyoushould– Oftenbettertouseadifferentname
• Ifyoumustprovideambiguousoverloadings,ensuresamebehaviorforsamearguments
public TreeSet(Collection<E> c); // Uses natural orderingpublic TreeSet(SortedSet<E> s); // Uses ordering from s
3915-214
Useappropriateparameter&returntypes
• Favorinterfacetypesoverclassesforinput– Providesflexibility,performance
• Usemostspecificpossibleinputparametertype– Moveserrorfromruntimetocompiletime
• Don'tuseString ifabettertypeexists– Stringsarecumbersome,error-prone,andslow
• Don'tusefloatingpointformonetaryvalues– Binaryfloatingpointcausesinexactresults!
• Usedouble (64bits)ratherthanfloat (32bits)– Precisionlossisreal,performancelossnegligible
4015-214
Useconsistentparameterorderingacrossmethods
• Especiallyimportantifparametertypesidentical#include <string.h>char *strncpy(char *dst, char *src, size_t n);void bcopy (void *src, void *dst, size_t n);
java.util.Collections – firstparameteralwayscollectiontobemodifiedorqueried
java.util.concurrent – timealwaysspecifiedaslongdelay,TimeUnit unit
4115-214
Avoidlongparameterlists
• Threeorfewerparametersisideal– Moreanduserswillhavetorefertodocs
• Longlistsofidenticallytypedparams harmful– Programmerstransposeparametersbymistake– Programsstillcompileandrun,butmisbehave!
• Techniquesforshorteningparameterlists– Breakupmethod– Createhelperclasstoholdparameters– BuilderPattern
// Eleven (!) parameters including > four consecutive intsHWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam);
4215-214
Avoidreturnvaluesthatdemandexceptionalprocessing• Returnzero-lengtharrayoremptycollection,notnull
package java.awt.image;public interface BufferedImageOp {// Returns the rendering hints for this operation,// or null if no hints have been set.public RenderingHints getRenderingHints();
}
4315-214
Outline
• TheProcessofAPIDesign• GeneralPrinciples• ClassDesign• MethodDesign• ExceptionDesign
4415-214
Throwexceptionstoindicateexceptionalconditions• Don’tforceclienttouseexceptionsforcontrolflow
private byte[] a = new byte[CHUNK_SIZE];
void processBuffer (ByteBuffer buf) {try {while (true) {buf.get(a);processBytes(a, CHUNK_SIZE);
}} catch (BufferUnderflowException e) {int remaining = buf.remaining();buf.get(a, 0, remaining);processBytes(a, remaining);
}}
• Conversely,don’tfailsilentlyThreadGroup.enumerate(Thread[] list)
4515-214
Favoruncheckedexceptions
• Checked– clientmusttakerecoveryaction• Unchecked– generallyaprogrammingerror• Overuseofcheckedexceptionscausesboilerplate
try { Foo f = (Foo) super.clone();....
} catch (CloneNotSupportedException e) { // This can't happen, since we’re Cloneablethrow new AssertionError();
}
4615-214
Includefailure-captureinformationinexceptions
• Allowsdiagnosisandrepairorrecovery• Foruncheckedexceptions,messagesuffices• Forcheckedexceptions,provideaccessors
4715-214
APIDesignSummary
• AgoodAPIisablessing;abadoneacurse• APIDesignishard,butyoucan’tescapeit– Acceptthefactthatweallmakemistakes– UseyourAPIsasyoudesignthem– Getfeedbackfromothers
• Thistalkcoveredsomeheuristicsofthecraft– Don'tadheretothemslavishly,but...– Don'tviolatethemwithoutgoodreason
4815-214
Outline
• TheProcessofAPIDesign• GeneralPrinciples• ClassDesign• MethodDesign• ExceptionDesign• TwoAPIrefactorings (butwe’reoutoftime)–Wedidn’tgooverthissectioninclass
4915-214
1.Sublist operationsinVectorpublic class Vector {
public int indexOf(Object elem, int index);public int lastIndexOf(Object elem, int index);...
}
• Notverypowerful- supportsonlysearch• Hardtousewithoutdocumentation
5015-214
Sublistoperationsrefactoredpublic interface List {
List subList(int fromIndex, int toIndex);...
}
• Extremelypowerful- supportsall operations• Useofinterfacereducesconceptualweight– Highpower-to-weightratio
• Easytousewithoutdocumentation
5115-214
2.Thread-localvariables
// Broken - inappropriate use of String as capability.// Keys constitute a shared global namespace.public class ThreadLocal {
private ThreadLocal() { } // Non-instantiable
// Sets current thread’s value for named variable.public static void set(String key, Object value);
// Returns current thread’s value for named variable.public static Object get(String key);
}
5215-214
Thread-localvariablesrefactored(1)public class ThreadLocal {
private ThreadLocal() { } // Noninstantiable
public static class Key { Key() { } }
// Generates a unique, unforgeable keypublic static Key getKey() { return new Key(); }
public static void set(Key key, Object value);public static Object get(Key key);
}
• Works,butrequiresboilerplatecodetousestatic ThreadLocal.Key serialNumberKey = ThreadLocal.getKey();ThreadLocal.set(serialNumberKey, nextSerialNumber());System.out.println(ThreadLocal.get(serialNumberKey));
5315-214
Thread-localvariablesrefactored(2)public class ThreadLocal<T> {
public ThreadLocal() { }public void set(T value);public T get();
}
• RemovesclutterfromAPIandclientcodestatic ThreadLocal<Integer> serialNumber =
new ThreadLocal<Integer>();serialNumber.set(nextSerialNumber());System.out.println(serialNumber.get());