前谷歌首席 Java 架構師談如何設優秀的 API – 碼農網 http://www.codeceo.com/article/google-java-good-api.htmlcss
隨着近來軟件規模的日益龐大,API編程接口的設計變的愈來愈重要。良好的接口設計能夠下降系統各部分之間的相互依賴,提升組成單元的內聚性,下降組成單元間的耦合度,從而提升系統的維護性和穩定性。java
Joshua Bloch是美國著名程序式設計師。他爲Java平臺設計並實現了許多的功能,是Google的首席Java架構師(Chief Java Architect)。他也是《Effective Java Programming Language Guide》一書的做者,就是人們常說的 Effective Java。本文翻譯自Joshua Bloch所發表的一個PPT:How to Design a Good API and Why it Matters。程序員
隨着大數據、公共平臺等互聯網技術的日益成熟,API接口的重要性日益凸顯,從公司的角度來看,API能夠算做是公司一筆巨大的資產,公共API能夠捕獲用戶、爲公司作出許多貢獻。對於我的來講,只要你編程,你就是一個API設計者,由於好的代碼便是模塊——每一個模塊即是一個API,而好的模塊會被屢次使用。此外,編寫API還有利於開發者提升代碼質量,提升自身的編碼水平。編程
優秀API所具有的特徵:swift
瞭解了一款優秀API所具有的特徵後,一塊兒再來看看如何設計優秀的API,有哪些流程和規則可循,開發者在設計時須要注意哪些事項。api
徵集需求數組
在開始以前,你可能會收到一些解決方案,它們不必定會比現有的方案好,而你的任務是以用例的形式提取真實需求,並制定真正合適的解決方案,這樣構建出來的東西就會更加有價值。架構
從簡短的說明開始app
這時,編寫簡短的說明最爲合適,編寫時須要考慮的因素有:
儘早編寫API
編寫SPI尤其重要
維護切實可行的指望
每一個API接口應該只專一一件事,並作好:若是它很難命名,那麼這或許是個很差的徵兆,好的名稱能夠驅動開發、而且只需拆分與合併模塊便可
if (car.speed() > 2 * SPEED_LIMIT) generateAlert("Watch out for cops!");
重視文檔
開發API時要意識到文檔的重要性。組件重用不是紙上談兵的東西,既須要好的設計,也須要優秀的文檔,這兩者缺一不可,即便咱們看到了良好的設計而未見文檔,那麼組件重用也是不妥的。
——摘自 D. L. Parnas 在1994年第16屆國際軟件開發大會上的演講內容
文檔應包含每一個類、接口、方法、構造函數、參數和異常,此外,還要當心對待文檔的狀態空間。
API設計決策對性能的影響
API與平臺和平共處
最小化可變性
子類只存在有意義的地方
用於繼承的設計和文檔或者直接禁止繼承(Design and Document for Inheritance or Else Prohibit it)
模塊能作到的,客戶端就不要作減小模板代碼的使用:
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. private 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)); } catch(TransformerException e) { throw new AssertionError(e); // Can’t happen! } }
遵照最小驚訝原則
用戶API只需根據需求來設計便可,沒必要讓客戶感到驚訝,當心弄巧成拙:
public class Thread implements Runnable { // Tests whether current thread has been interrupted. // Clears the interrupted status of current thread. public static boolean interrupted(); }
故障快速報告應儘快生成
// 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 strings public void save(OutputStream out, String comments); }
以String形式對全部可用數據提供編程式訪問
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(); }
方法重載要細心
public TreeSet(Collection c); // Ignores order public TreeSet(SortedSet s); // Respects order
使用合適的參數和返回類型
#include char *strcpy (char *dest, char *src); void bcopy (void *src, void *dst, int n);
java.util.Collections – first parameter always collection to be modified
or queried
java.util.concurrent – time always specified as long delay, TimeUnit
unit
避免使用長參數列表
最好避免這種狀況出現:
// Eleven parameters including four consecutive ints
HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam);
返回值勿需進行異常處理
好比,返回零長度字符串或者空集合
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(); }
拋出異常來講明異常情況;不要強迫客戶端使用異常來控制流。
private byte[] a = new byte[BUF_SIZE]; void processBuffer (ByteBuffer buf) { try { while (true) { buf.get(a); processBytes(tmp, BUF_SIZE); } } catch (BufferUnderflowException e) { int remaining = buf.remaining(); buf.get(a, 0, remaining); processBytes(bufArray, remaining); } }
Conversely, don’t fail silently
ThreadGroup.enumerate(Thread[] list)
支持Unchecked Exceptions
try {
Foo f = (Foo) super.clone(); .... } catch (CloneNotSupportedException e) { // This can't happen, since we’re Cloneable throw new AssertionError(); }
異常中應該包含捕獲錯誤的(Failure-Capture)信息
在Vector中進行Sublist操做
public class Vector { public int indexOf(Object elem, int index); public int lastIndexOf(Object elem, int index); ... }
分析:
重構Sublist操做
public interface List { List subList(int fromIndex, int toIndex); ... }
分析:
線程局部變量
// 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); }
線程局部變量重構1
public class ThreadLocal { private ThreadLocal() { } // Noninstantiable public static class Key { Key() { } } // Generates a unique, unforgeable key public static Key getKey() { return new Key(); } public static void set(Key key, Object value); public static Object get(Key key); }
能夠運行,可是須要使用模板代碼。
static ThreadLocal.Key serialNumberKey = ThreadLocal.getKey(); ThreadLocal.set(serialNumberKey, nextSerialNumber()); System.out.println(ThreadLocal.get(serialNumberKey));
線程局部變量重構2
public class ThreadLocal { public ThreadLocal() { } public void set(Object value); public Object get(); }
從API和客戶端代碼中刪除了無用代碼。
static ThreadLocal serialNumber = new ThreadLocal(); serialNumber.set(nextSerialNumber()); System.out.println(serialNumber.get());
API設計是一件很是高端大氣上檔次的工藝,對程序員、終端用戶和公司都會有所提高。不要盲目地去遵照文中所說起的規則、說明等,但也不要去侵犯他們,API設計不是件簡單的工藝,也不是一種能夠孤立行動的活。固然完美永遠沒法實現,但咱們要努力去追求完美。
Google首席工程師Joshua Bloch談如何設計優秀的API - Beaver - CSDN博客 https://blog.csdn.net/doctor_who2004/article/details/52014667
Google首席工程師Joshua Bloch談如何設計優秀的API
How to Design a Good API and Why it Matters
中文版:http://www.codeceo.com/article/google-java-good-api.html
Why is API Design Important?
APIs can be among a company's greatest assets
_ Customers invest heavily: buying, writing, learning
_ Cost to stop using an API can be prohibitive
_ Successful public APIs capture customers
Can also be among company's greatest liabilities
_Bad APIs result in unending stream of support calls
• Public APIs are forever - one chance to get it right
Why is API Design Importantto You?
If you program, you are an API designer
_Good code is modular–each module has an API
Useful modules tend to get reused
_ Once module has users, can’t change API at will
_ Good reusable modules are corporate assets
Thinking in terms of APIs improves code quality
Characteristics of a Good API
Easy to learn
Easy to use, even without documentation
Hard to misuse
Easy to read and maintain code that uses it
Sufficiently powerful to satisfy requirements
Easy to extend
Appropriate to audience
Outline
I. The Process of API Design
II. General Principles
III. Class Design
IV. Method Design
V. Exception Design
VI. Refactoring API Designs
The Process of API Design
Gather Requirements–with a Healthy Degree of Skepticism
Often you'll get proposed solutions instead
_Better solutions may exist
Your job is to extract true requirements
_Should take the form ofuse-cases
Can be easier and more rewarding to build something more general
Start with Short Spec–1 Page is Ideal
•At this stage, agility trumps completeness
•Bounce spec off as many people as possible
_Listen to their input and take it seriously
• If you keep the spec short, it’s easy to modify
• Flesh it out as you gain confidence
_This necessarily involves coding
Write to Your API Early and Often
•Start before you've implemented the API
_ Saves you doing implementation you'll throw away
• Start before you've even specified it properly
_ Saves you from writing specs you'll throw away
• Continue writing to API as you flesh it out
_ Prevents nasty surprises
_ Code lives on as examples, unit tests
Writing to SPI is Even More Important
•Service Provider Interface (SPI)
_Plug-in interface enabling multiple implementations
_Example: Java Cryptography Extension (JCE)
•Write multiple plug-ins before release
_ If you write one, it probably won't support another
_ If you write two, it will support more with difficulty
_ If you write three, it will work fine
• Will Tracz calls this 「The Rule of Threes」
(Confessions of a Used Program Salesman, Addison-Wesley, 1995)
Maintain Realistic Expectations
•Most API designs are over-constrained
_ You won't be able to please everyone
_Aim to displease everyone equally
• Expect to make mistakes
_ A few years of real-world use will flush them out
_ Expect to evolve API
General Principles
API Should Do One Thing and Do it Well
•Functionality should be easy to explain
_ If it's hard to name, that's generally a bad sign
_Good names drive development
_Be amenable to splitting and merging modules
API Should Be As Small As Possible But No Smaller
•API should satisfy its requirements
• When in doubt leave it out
_ Functionality, classes, methods, parameters, etc.
_ You can always add, but you can never remove
• Conceptual weight more important than bulk
• Look for a good power-to-weight ratio
Implementation Should Not Impact API
•Implementation details
_ Confuse users
_Inhibit freedom to change implementation
• Be aware of what is an implementation detail
_Do not overspecify the behavior of methods
_ For example: do not specify hash functions
_All tuning parameters are suspect
• Don't let implementation details 「leak」 into API
_ On-disk and on-the-wire formats, exceptions
Minimize Accessibility of Everything
•Make classes and members as private as possible
• Public classes should have no public fields(with the exception of constants)
• This maximizes information hiding
• Allows modules to be used, understood, built,tested, and debugged independently
Names Matter–API is a Little Language
•Names Should Be Largely Self-Explanatory
_ Avoid cryptic abbreviations
• Be consistent–same word means same thing
_ Throughout API, (Across APIs on the platform)
• Be regular–strive for symmetry
• Code should read like prose
if (car.speed() > 2 * SPEED_LIMIT)
generateAlert("Watch out for cops!");
Documentation Matters
Reuse is something that is far easier to say than
to do. Doing it requires both good design and
very good documentation. Even when we see
good design, which is still infrequently, we won't
see the components reused without good documentation.
- D. L. Parnas, _Software Aging. Proceedings of 16th International Conference Software Engineering, 1994
Document Religiously
•Document every class, interface, method,constructor, parameter, and exception
_ Class: what an instance represents
_Method: contract between method and its client
_ Preconditions, postconditions, side-effects
_ Parameter: indicate units, form, ownership
• Document state space very carefully
Consider Performance Consequences of API Design Decisions
•Bad decisions can limit performance
_ Making type mutable
_ Providing constructor instead of static factory
_ Using implementation type instead of interface
• Do not warp API to gain performance
_ Underlying performance issue will get fixed,but headaches will be with you forever
_ Good design usually coincides with good performance
Effects of API Design Decisions on Performance are Real and Permanent
•Component.getSize()returns Dimension
•Dimension is mutable
• EachgetSize call must allocateDimension
• Causes millions of needless object allocations
• Alternative added in 1.2; old client code still slow
API Must Coexist Peacefully with Platform
•Do what is customary
_Obey standard naming conventions
_Avoid obsolete parameter and return types
_Mimic patterns in core APIs and language
• Take advantage of API-friendly features
_ Generics, varargs, enums, default arguments
• Know and avoid API traps and pitfalls
_Finalizers, public static final arrays
Class Design
Minimize Mutability
•Classes should be immutable unless there’s a good reason to do otherwise
_Advantages: simple, thread-safe, reusable
_Disadvantage: separate object for each value
•If mutable, keep state-space small, well-defined
_Make clear when it's legal to call which method
Bad:Date, Calendar
Good: TimerTask
Subclass Only Where It Makes Sense
•Subclassing implies substitutability (Liskov)
_Subclass only when is-a relationship exists
_Otherwise, use composition
• Public classes should not subclass other public classes for ease of implementation
Bad: Properties extends Hashtable
Stack extends Vector
Good: Set extends Collection
Design and Document for Inheritance or Else Prohibit it
•Inheritance violates encapsulation (Snyder, ‘86)
_Subclass sensitive to implementation details of superclass
•If you allow subclassing, documentself-use
_How do methods use one another?
• Conservative policy: all concrete classes final
Bad: Many concrete classes in J2SE libraries
Good: AbstractSet, AbstractMap
Method Design
Don't Make the Client Do Anything the Module Could Do
•Reduce need for boilerplate code
_ Generally done via cut-and-paste
_ Ugly, annoying, and error-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.
private 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));
} catch(TransformerException e) {
throw new AssertionError(e); // Can’t happen!
}
}
Don't Violate the Principle of Least Astonishment
•User of API should not be surprised by behavior
_ It's worth extra implementation effort
_ It's even worth reduced performance
public class Thread implements Runnable {
// Tests whether current thread has been interrupted.
// Clears the interrupted status of current thread.
public static boolean interrupted();
}
Fail Fast–Report Errors as Soon as Possible After They Occur
•Compile time is best - static typing, generics
• At runtime, first bad method invocation is best
_ Method should be failure-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 strings
public void save(OutputStream out, String comments);
}
Provide Programmatic Access to All Data Available in String Form
•Otherwise, clients will parse strings
_ Painful for clients
_ Worse, turns string format into de facto API
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();
}
Overload With Care
•Avoid ambiguous overloadings
_ Multiple overloadings applicable to same actuals
_ Conservative: no two with same number of args
• Just because you can doesn't mean you should
_Often better to use a different name
• If you must provide ambiguous overloadings, ensure same behavior for same arguments
public TreeSet(Collection c); // Ignores order
public TreeSet(SortedSet s); // Respects order
Use Appropriate Parameter and Return Types
•Favor interface types over classes for input
_Provides flexibility, performance
• Use most specific possible input parameter type
_ Moves error from runtime to compile time
• Don't use string if a better type exists
_ Strings are cumbersome, error-prone, and slow
• Don't use floating point for monetary values
_ Binary floating point causes inexact results!
• Use double (64 bits) rather than float (32 bits)
_ Precision loss is real, performance loss negligible
Use Consistent Parameter Ordering Across Methods
•Especially important if parameter types identical
#include <string.h>
char *strcpy (char *dest, char *src);
void bcopy (void *src, void *dst, int n);
java.util.Collections – first parameter always
collection to be modified or queried
java.util.concurrent – time always specified as
long delay, TimeUnit unit
Avoid Long Parameter Lists
•Three or fewer parameters is ideal
_ More and users will have to refer to docs
• Long lists of identically typed params harmful
_ Programmers transpose parameters by mistake
_ Programs still compile, run, but misbehave!
• Two techniques for shortening parameter lists
_ Break up method
_ Create helper class to hold parameters
// Eleven parameters including four consecutive ints
HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
LPVOID lpParam);
Avoid Return Values that Demand Exceptional Processing
•return zero-length array or empty collection, not null
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();
}
Exception Design
Throw Exceptions to Indicate Exceptional Conditions
•Don’t force client to use exceptions for control flow
private byte[] a = new byte[BUF_SIZE];
void processBuffer (ByteBuffer buf) {
try {
while (true) {
buf.get(a);
processBytes(tmp, BUF_SIZE);
}
} catch (BufferUnderflowException e) {
int remaining = buf.remaining();
buf.get(a, 0, remaining);
processBytes(bufArray, remaining);
}
}
• Conversely, don’t fail silently
ThreadGroup.enumerate(Thread[] list)
Favor Unchecked Exceptions
•Checked – client must take recovery action
• Unchecked – programming error
• Overuse of checked exceptions causes boilerplate
try {
Foo f = (Foo) super.clone();
....
} catch (CloneNotSupportedException e) {
// This can't happen, since we’re Cloneable
throw new AssertionError();
}
Include Failure-Capture Information in Exceptions
•Allows diagnosis and repair or recovery
• For unchecked exceptions, message suffices
• For checked exceptions, provide accessors
Refactoring API Designs
Sublist Operations in Vector
public class Vector {
public int indexOf(Object elem, int index);
public int lastIndexOf(Object elem, int index);
...
} •
Not very powerful - supports only search
• Hard too use without documentation
Sublist Operations Refactored
public interface List {
List subList(int fromIndex, int toIndex);
...
} •
Extremely powerful - supports all operations
• Use of interface reduces conceptual weight
_ High power-to-weight ratio
• Easy to use without documentation
Thread-Local Variables
// 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);
}
Thread-Local Variables Refactored (1)
public class ThreadLocal {
private ThreadLocal() { } // Noninstantiable
public static class Key { Key() { } }
// Generates a unique, unforgeable key
public static Key getKey() { return new Key(); }
public static void set(Key key, Object value);
public static Object get(Key key);
}
• Works, but requires boilerplate code to use
static ThreadLocal.Key serialNumberKey = ThreadLocal.getKey();
ThreadLocal.set(serialNumberKey, nextSerialNumber());
System.out.println(ThreadLocal.get(serialNumberKey));
Thread-Local Variables Refactored (2)
public class ThreadLocal {
public ThreadLocal() { }
public void set(Object value);
public Object get();
}
• Removes clutter from API and client code
static ThreadLocal serialNumber = new ThreadLocal();
serialNumber.set(nextSerialNumber());
System.out.println(serialNumber.get());
Conclusion
•API design is a noble and rewarding craft_ Improves the lot of programmers, end-users, companies• This talk covered some heuristics of the craft_ Don't adhere to them slavishly, but..._Don't violate them without good reason• API design is tough_ Not a solitary activity_ Perfection is unachievable, but try anyway---------------------