Java风格指南
本指南的目的是提供一组鼓励良好代码的约定。它是许多软件工程和Java开发经验相结合的精华。虽然有些建议比其他建议更严格,但你应该始终保持良好的判断力。
如果遵循指南导致不必要的箍跳或其他不太可读的代码,则 可读性胜过指南。但是,如果更“可读”的变体带来危险或陷阱,则可能会牺牲可读性。
通常,我们的许多样式和约定都反映了Java编程语言的 代码约定 和Google的Java样式指南。
目录
- 编码风格
格式化
字段,类和方法声明
变量命名
太空垫操作员和平等
明确运算符优先级
文档
进口
明智地使用注释
使用接口 - 编写可测试的代码
假货和嘲笑
让您的调用者构建支持对象
测试多线程代码
测试反模式 - 避免测试中的随机性
- 最佳做法
防守编程
清洁代码
使用更新/更好的库
equals()和hashCode()
过早优化是万恶之源
待办事项
遵守得墨忒耳法则
不要重复自己
正确管理线程
避免不必要的代码
“快速”实施
编码风格
格式化
明智地使用换行符插入换行符通常有两个原因:
1.您的陈述超出了列限制。
2.你想在逻辑上分开思想。
编写代码就像讲故事一样。书面语言结构如章节,段落和标点符号(例如分号,逗号,句号,连字符)传达了思想层次和分离。我们在编程语言中有类似的结构; 您应该利用它们来有效地将故事告诉那些阅读代码的人。
缩进风格
我们使用“一个真正的支撑式”(1TBS)。缩进大小为2列。
:::java
// Like this.
if (x < 0) {negative(x);
} else {nonnegative(x);
}// Not like this.
if (x < 0)negative(x);// Also not like this.
if (x < 0) negative(x);
延续缩进是4列。嵌套延续可以在每个级别添加4列或2列。
:::java
// Bad.
// - Line breaks are arbitrary.
// - Scanning the code makes it difficult to piece the message together.
throw new IllegalStateException("Failed to process request" + request.getId()+ " for user " + user.getId() + " query: '" + query.getText()+ "'");// Good.
// - Each component of the message is separate and self-contained.
// - Adding or removing a component of the message requires minimal reformatting.
throw new IllegalStateException("Failed to process"+ " request " + request.getId()+ " for user " + user.getId()+ " query: '" + query.getText() + "'");
不要不必要地破坏声明。
:::java
// Bad.
final String value =otherValue;// Good.
final String value = otherValue;
方法声明延续。
:::java
// Sub-optimal since line breaks are arbitrary and only filling lines.
String downloadAnInternet(Internet internet, Tubes tubes,Blogosphere blogs, Amount<Long, Data> bandwidth) {tubes.download(internet);...
}// Acceptable.
String downloadAnInternet(Internet internet, Tubes tubes, Blogosphere blogs,Amount<Long, Data> bandwidth) {tubes.download(internet);...
}// Nicer, as the extra newline gives visual separation to the method body.
String downloadAnInternet(Internet internet, Tubes tubes, Blogosphere blogs,Amount<Long, Data> bandwidth) {tubes.download(internet);...
}// Also acceptable, but may be awkward depending on the column depth of the opening parenthesis.
public String downloadAnInternet(Internet internet,Tubes tubes,Blogosphere blogs,Amount<Long, Data> bandwidth) {tubes.download(internet);...
}// Preferred for easy scanning and extra column space.
public String downloadAnInternet(Internet internet,Tubes tubes,Blogosphere blogs,Amount<Long, Data> bandwidth) {tubes.download(internet);...
}
链式方法调用
:::java
// Bad.
// - Line breaks are based on line length, not logic.
Iterable<Module> modules = ImmutableList.<Module>builder().add(new LifecycleModule()).add(new AppLauncherModule()).addAll(application.getModules()).build();// Better.
// - Calls are logically separated.
// - However, the trailing period logically splits a statement across two lines.
Iterable<Module> modules = ImmutableList.<Module>builder().add(new LifecycleModule()).add(new AppLauncherModule()).addAll(application.getModules()).build();// Good.
// - Method calls are isolated to a line.
// - The proper location for a new method call is unambiguous.
Iterable<Module> modules = ImmutableList.<Module>builder().add(new LifecycleModule()).add(new AppLauncherModule()).addAll(application.getModules()).build();
没有标签
一个老人,但好。我们发现制表符会造成更多弊大于利。
100列限制
您应该遵循您正在使用的代码体设置的约定。我们倾向于使用100列来减少延续线之间的平衡,但仍然可以在相当高分辨率的显示器上并排放置两个编辑器标签。
CamelCase用于类型,camelCase用于变量,UPPER_SNAKE用于常量
没有尾随空格
尾随空白字符虽然在逻辑上是良性的,但不会向程序添加任何内容。但是,当使用键盘快捷键导航代码时,它们确实会使开发人员感到沮丧。
字段,类和方法声明
修饰符顺序
我们遵循Java语言规范进行修饰符排序(第8.1.1节,第 8.3.1节 和第 8.4.3节)。
:::java
// Bad.
final volatile private String value;// Good.
private final volatile String value;
变量命名
应该为循环索引等实例保留极短的变量名。
:::java
// Bad.
// - Field names give little insight into what fields are used for.
class User {private final int a;private final String m;...
}// Good.
class User {private final int ageInYears;private final String maidenName;...
}
包含变量名称的单位
:::java
// Bad.
long pollInterval;
int fileSize;// Good.
long pollIntervalMs;
int fileSizeGb.// Better.
// - Unit is built in to the type.
// - The field is easily adaptable between units, readability is high.
Amount<Long, Time> pollInterval;
Amount<Integer, Data> fileSize;
不要在变量名中嵌入元数据
变量名称应描述变量的用途。添加范围和类型等额外信息通常是变量名称错误的标志。避免在字段名称中嵌入字段类型。
:::java
// Bad.
Map<Integer, User> idToUserMap;
String valueString;// Good.
Map<Integer, User> usersById;
String value;
还要避免将范围信息嵌入变量中。基于层次结构的命名表明类太复杂,应该分开。
:::java
// Bad.
String _value;
String mValue;// Good.
String value;
太空垫操作员和平等。
:::java
// Bad.
// - This offers poor visual separation of operations.
int foo=a+b+1;// Good.
int foo = a + b + 1;
明确运算符优先级
不要让读者打开 规范来确认,如果你期望特定的操作顺序,用括号明确表示。
:::java
// Bad.
return a << 8 * n + 1 | 0xFF;// Good.
return (a << (8 * n) + 1) | 0xFF;
真的很明显是件好事。
:::java
if ((values != null) && (10 > values.size())) {...
}
文档
一段代码越明显(并且通过扩展 - 消费者可能越远),需要的文档越多。
“我正在写一篇关于…的报道。”
你的小学老师是对的 - 你不应该这样开始说话。同样,您不应该以这种方式编写文档。
:::java
// Bad.
/*** This is a class that implements a cache. It does caching for you.*/
class Cache {...
}// Good.
/*** A volatile storage for objects based on a key, which may be invalidated and discarded.*/
class Cache {...
}
记录课程
类的文档可以从单个句子到包含代码示例的段落。文档应该用于消除API中任何概念空白的歧义,并使您更容易快速正确地使用API??。一个彻底的课程通常有一个句子摘要,如有必要,还有一个更详细的解释。
:::java
/*** An RPC equivalent of a unix pipe tee. Any RPC sent to the tee input is guaranteed to have* been sent to both tee outputs before the call returns.** @param <T> The type of the tee'd service.*/
public class RpcTee<T> {...
}
记录方法
方法doc应该告诉方法的作用。根据参数类型,记录输入格式也很重要。
:::java
// Bad.
// - The doc tells nothing that the method declaration didn't.
// - This is the 'filler doc'. It would pass style checks, but doesn't help anybody.
/*** Splits a string.** @param s A string.* @return A list of strings.*/
List<String> split(String s);// Better.
// - We know what the method splits on.
// - Still some undefined behavior.
/*** Splits a string on whitespace.** @param s The string to split. An {@code null} string is treated as an empty string.* @return A list of the whitespace-delimited parts of the input.*/
List<String> split(String s);// Great.
// - Covers yet another edge case.
/*** Splits a string on whitespace. Repeated whitespace characters are collapsed.** @param s The string to split. An {@code null} string is treated as an empty string.* @return A list of the whitespace-delimited parts of the input.*/
List<String> split(String s);
要专业
在与其他图书馆打交道时,我们都遇到了挫败感,但对它进行咆哮并没有给你任何好处。抑制咒骂并达到目的。
:::java
// Bad.
// I hate xml/soap so much, why can't it do this for me!?
try {userId = Integer.parseInt(xml.getField("id"));
} catch (NumberFormatException e) {...
}// Good.
// TODO(Jim): Tuck field validation away in a library.
try {userId = Integer.parseInt(xml.getField("id"));
} catch (NumberFormatException e) {...
}
不要记录重写方法(通常)
:::java
interface Database {/*** Gets the installed version of the database.** @return The database version identifier.*/String getVersion();
}// Bad.
// - Overriding method doc doesn't add anything.
class PostgresDatabase implements Database {/*** Gets the installed version of the database.** @return The database version identifier.*/@Overridepublic String getVersion() {...}
}// Good.
class PostgresDatabase implements Database {@Overridepublic int getVersion();
}// Great.
// - The doc explains how it differs from or adds to the interface doc.
class TwitterDatabase implements Database {/*** Semantic version number.** @return The database version in semver format.*/@Overridepublic String getVersion() {...}
}
使用javadoc功能
没有作者标签
代码在其生命周期中可以多次转手,并且经常在多次迭代后源文件的原始作者无关紧要。我们发现最好信任提交历史和OWNERS文件来确定代码体的所有权。
进口
导入订购
导入按顶级包分组,空行分隔组。静态导入以相同的方式分组,在传统导入下面的部分中。
:::java
import java.*
import javax.*import scala.*import com.*import net.*import org.*import com.twitter.*import static *
没有通配符导入
通配符导入使导入类的源不太清楚。他们也倾向于隐藏高级别的粉丝。
另见德州进口
:::java
// Bad.
// - Where did Foo come from?
import com.twitter.baz.foo.*;
import com.twitter.*;interface Bar extends Foo {...
}// Good.
import com.twitter.baz.foo.BazFoo;
import com.twitter.Foo;interface Bar extends Foo {...
}
明智地使用注释
@Nullable
默认情况下 - 禁止null。当变量,参数或方法返回值可能是null,通过标记@Nullable来明确它 。即使对于具有私人可见性的字段/方法,这也是可取的。
:::java
class Database {@Nullable private Connection connection;@NullableConnection getConnection() {return connection;}void setConnection(@Nullable Connection connection) {this.connection = connection;}
}
@VisibleForTesting
有时一般隐藏成员和功能是有意义的,但它们可能仍然需要良好的测试覆盖率。通常首选使用@VisibleForTesting创建这些package-private和tag 以指示可见性的目的。
常量是经常以这种方式暴露的事物的一个很好的例子。
:::java
// Bad.
// - Any adjustments to field names need to be duplicated in the test.
class ConfigReader {private static final String USER_FIELD = "user";Config parseConfig(String configData) {...}
}
public class ConfigReaderTest {@Testpublic void testParseConfig() {...assertEquals(expectedConfig, reader.parseConfig("{user: bob}"));}
}// Good.
// - The test borrows directly from the same constant.
class ConfigReader {@VisibleForTesting static final String USER_FIELD = "user";Config parseConfig(String configData) {...}
}
public class ConfigReaderTest {@Testpublic void testParseConfig() {...assertEquals(expectedConfig,reader.parseConfig(String.format("{%s: bob}", ConfigReader.USER_FIELD)));}
}
使用接口
接口将功能与实现分离,允许您在不改变使用者的情况下使用多个实现。接口是隔离包的好方法 - 提供一组接口,并使您的实现包保持私有。
许多小接口看起来很重,因为你最终得到了大量的源文件。考虑下面的模式作为替代。
:::java
interface FileFetcher {File getFile(String name);// All the benefits of an interface, with little source management overhead.// This is particularly useful when you only expect one implementation of an interface.static class HdfsFileFetcher implements FileFetcher {@Override File getFile(String name) {...}}
}
利用或扩展现有接口
有时,现有的界面允许您的班级轻松“插入”其他相关课程。这导致了高度内聚的代码。
:::java
// An unfortunate lack of consideration. Anyone who wants to interact with Blobs will need to
// write specific glue code.
class Blobs {byte[] nextBlob() {...}
}// Much better. Now the caller can easily adapt this to standard collections, or do more
// complex things like filtering.
class Blobs implements Iterable<byte[]> {@OverrideIterator<byte[]> iterator() {...}
}
警告 - 不要弯曲现有接口的定义以使其工作。如果界面在概念上没有干净利落,最好避免这种情况。
编写可测试的代码
编写单元测试不一定很难。如果在设计类和接口时牢记可测试性,那么您可以轻松自己。
假货和嘲笑
在测试类时,您通常需要提供某种类型的预制功能来替代实际行为。例如,您没有从真实数据库中获取行,而是要返回一个测试行。这通常是使用假对象或模拟对象执行的。虽然差异听起来很微妙,但嘲笑比假货有很大的好处。
:::java
class RpcClient {RpcClient(HttpTransport transport) {...}
}// Bad.
// - Our test has little control over method call order and frequency.
// - We need to be careful that changes to HttpTransport don't disable FakeHttpTransport.
// - May require a significant amount of code.
class FakeHttpTransport extends HttpTransport {@Overridevoid writeBytes(byte[] bytes) {...}@Overridebyte[] readBytes() {...}
}public class RpcClientTest {private RpcClient client;private FakeHttpTransport transport;@Beforepublic void setUp() {transport = new FakeHttpTransport();client = new RpcClient(transport);}...
}interface Transport {void writeBytes(byte[] bytes);byte[] readBytes();
}class RpcClient {RpcClient(Transport transport) {...}
}// Good.
// - We can mock the interface and have very fine control over how it is expected to be used.
public class RpcClientTest {private RpcClient client;private Transport transport;@Beforepublic void setUp() {transport = EasyMock.createMock(Transport.class);client = new RpcClient(transport);}...
}
让您的调用者构建支持对象
:::java
// Bad.
// - A unit test needs to manage a temporary file on disk to test this class.
class ConfigReader {private final InputStream configStream;ConfigReader(String fileName) throws IOException {this.configStream = new FileInputStream(fileName);}
}// Good.
// - Testing this class is as easy as using ByteArrayInputStream with a String.
class ConfigReader {private final InputStream configStream;ConfigReader(InputStream configStream){this.configStream = checkNotNull(configStream);}
}
测试多线程代码
测试使用多线程的代码是非常困难的。但是,仔细接近时,可以在没有死锁或不必要的时间等待语句的情况下完成。
如果要测试需要执行定期后台任务的代码(例如使用 ScheduledExecutorService),请考虑模拟服务和/或手动触发测试中的任务,并避免实际调度。如果您正在测试向ExecutorService提交任务的代码 ,您可以考虑允许注入执行程序,并在测试中提供 单线程执行程序。
在多线程不可避免的情况下, java.util.concurrent 提供了一些有用的库来帮助管理锁步执行。
例如, LinkedBlockingDeque 可以在执行异步操作时提供生产者和使用者之间的同步。 当队列不适用时,CountDownLatch对状态/操作同步很有用。
测试反模式
时间依赖性
捕获实际墙壁时间的代码可能难以重复测试,尤其是在时间增量有意义的情况下。因此,尽量避免new Date(),System.currentTimeMillis()和 System.nanoTime()。适合这些的替代品是 Clock ; 使用 Clock.SYSTEM_CLOCK 正常运行时,与 FakeClock 在测试中。
隐藏的压力测试
避免编写试图验证一定性能的单元测试。这种类型的测试应该单独处理,并且在比单元测试通常更受控制的环境中运行。
Thread.sleep()方法
很少有人保证睡觉,特别是在测试代码中。睡眠表示期望在执行线程暂停时发生其他事情。这很快就会导致脆弱; 例如,如果您在睡觉时未安排后台线程。
在测试中睡觉也很糟糕,因为它确定了测试执行速度的下限。无论机器有多快,睡眠一秒钟的测试都不会在不到一秒的时间内完成。随着时间的推移,这会导致很长的测试执行周期。
避免测试中的随机性
在测试中使用随机值似乎是一个好主意,因为它允许您用更少的代码覆盖更多的测试用例。问题是你无法控制你所覆盖的测试用例。当您遇到测试失败时,可能难以重现。具有固定种子的伪随机输入稍微好一点,但实际上很少提高测试覆盖率。通常,最好使用固定的输入数据来处理已知的边缘情况。
最佳做法
防守编程
避免断言
我们避免使用assert语句,因为它可以 在执行时被 禁用,并且更喜欢在任何时候强制执行这些类型的不变量。
另见前提条件
前提条件
前置条件检查是一种很好的做法,因为它们可以作为明确的屏障,防止来自呼叫者的错误输入。作为约定,应始终针对null检查公共构造函数和方法的对象参数,除非明确允许null。
另见@ null,@ Nullable
:::java
// Bad.
// - If the file or callback are null, the problem isn't noticed until much later.
class AsyncFileReader {void readLater(File file, Closure<String> callback) {scheduledExecutor.schedule(new Runnable() {@Override public void run() {callback.execute(readSync(file));}}, 1L, TimeUnit.HOURS);}
}// Good.
class AsyncFileReader {void readLater(File file, Closure<String> callback) {checkNotNull(file);checkArgument(file.exists() && file.canRead(), "File must exist and be readable.");checkNotNull(callback);scheduledExecutor.schedule(new Runnable() {@Override public void run() {callback.execute(readSync(file));}}, 1L, TimeUnit.HOURS);}
}
最小化可见性
在类API中,您应该支持访问您可以访问的任何方法和字段。因此,只暴露您打算使用的调用者。在编写线程安全代码时,这是必不可少的。
:::java
public class Parser {// Bad.// - Callers can directly access and mutate, possibly breaking internal assumptions.public Map<String, String> rawFields;// Bad.// - This is probably intended to be an internal utility function.public String readConfigLine() {..}
}// Good.
// - rawFields and the utility function are hidden
// - The class is package-private, indicating that it should only be accessed indirectly.
class Parser {private final Map<String, String> rawFields;private String readConfigLine() {..}
}
赞成不变性
可变对象带来负担 - 您需要确保那些能够改变它的人不会违反对象的其他用户的期望,并且他们甚至可以安全地进行修改。
:::java
// Bad.
// - Anyone with a reference to User can modify the user's birthday.
// - Calling getAttributes() gives mutable access to the underlying map.
public class User {public Date birthday;private final Map<String, String> attributes = Maps.newHashMap();...public Map<String, String> getAttributes() {return attributes;}
}// Good.
public class User {private final Date birthday;private final Map<String, String> attributes = Maps.newHashMap();...public Map<String, String> getAttributes() {return ImmutableMap.copyOf(attributes);}// If you realize the users don't need the full map, you can avoid the map copy// by providing access to individual members.@Nullablepublic String getAttribute(String attributeName) {return attributes.get(attributeName);}
}
警惕null
使用@Nullable其中审慎的,但有利于 可选 了@Nullable。 Optional在没有值的情况下提供更好的语义。
终于清理干净了
:::java
FileInputStream in = null;
try {...
} catch (IOException e) {...
} finally {Closeables.closeQuietly(in);
}
即使没有经过检查的异常,仍然存在使用try / finally保证资源对称性的情况。
:::java
// Bad.
// - Mutex is never unlocked.
mutex.lock();
throw new NullPointerException();
mutex.unlock();// Good.
mutex.lock();
try {throw new NullPointerException();
} finally {mutex.unlock();
}// Bad.
// - Connection is not closed if sendMessage throws.
if (receivedBadMessage) {conn.sendMessage("Bad request.");conn.close();
}// Good.
if (receivedBadMessage) {try {conn.sendMessage("Bad request.");} finally {conn.close();}
}
清洁代码
歧义
赞成可读性 - 如果有一个模糊和明确的路线,总是赞成明确。
:::java
// Bad.
// - Depending on the font, it may be difficult to discern 1001 from 100l.
long count = 100l + n;// Good.
long count = 100L + n;
删除死代码
删除未使用的代码(导入,字段,参数,方法,类)。他们只会腐烂。
使用一般类型
在声明字段和方法时,最好尽可能使用常规类型。这可以避免通过API泄漏实现细节,并允许您更改内部使用的类型,而不会影响用户或外围代码。
:::java
// Bad.
// - Implementations of Database must match the ArrayList return type.
// - Changing return type to Set<User> or List<User> could break implementations and users.
interface Database {ArrayList<User> fetchUsers(String query);
}// Good.
// - Iterable defines the minimal functionality required of the return.
interface Database {Iterable<User> fetchUsers(String query);
}
始终使用类型参数
Java 5引入了对泛型的支持 。这为集合类型添加了类型参数,并允许用户实现自己的类型参数化类。向后兼容性和 类型擦除意味着类型参数是可选的,但是根据使用情况,它们会导致编译器警告。
我们通常在参数化类型的每个声明中包含类型参数。即使类型未知,也最好包含通配符或宽类型。
远离德克萨斯州
尽量保持你的课程大小和明确的责任。随着程序的发展,这可能 非常困难。
得克萨斯州进口
得克萨斯州的建设者:班级可以彻底拆散吗?
如果没有,请考虑构建器模式。
得克萨斯方法
我们可以做一些科学研究并为每一项提出统计驱动的阈值,但它可能不会非常有用。这通常只是一种直觉,而这些是太大或太复杂的类的特征,应该被分解。
避免类型转换
类型转换是阶级设计不佳的标志,通常可以避免。一个明显的例外是覆盖 平等。
使用最终字段
另见支持不变性
最终字段很有用,因为它们声明可能无法重新分配字段。在检查线程安全性方面,最后一个字段是需要检查的一件事。
避免可变的静态
很少需要可变的静态,并且当存在时会引起大量问题。可变静态复杂化的一个非常简单的情况是单元测试。由于单元测试运行通常在单个VM中,因此静态状态将在所有测试用例中持续存在。一般来说,可变静态是一种糟糕的类设计的标志。
例外
抓住狭隘的例外
有时使用try / catch块时,它可能是很有诱惑力的公正catch Exception,Error或者Throwable让你不用担心被抛出什么类型。这通常是一个坏主意,因为你最终可能会捕获比你真正想要处理的更多。例如, catch Exception将捕获NullPointerException并catch Throwable捕获 OutOfMemoryError。
:::java
// Bad.
// - If a RuntimeException happens, the program continues rather than aborting.
try {storage.insertUser(user);
} catch (Exception e) {LOG.error("Failed to insert user.");
}try {storage.insertUser(user);
} catch (StorageException e) {LOG.error("Failed to insert user.");
}
不要吞下例外
空catch块通常是一个坏主意,因为你没有问题的信号。再加上 狭隘的异常违规行为,它就是一场灾难。
中断时,重置线程中断状态
许多阻塞操作抛出 InterruptedException, 以便您可以唤醒JVM关闭等事件。捕获时InterruptedException,最好确保保留线程中断状态。
IBM有一篇关于这个主题的好文章。
:::java
// Bad.
// - Surrounding code (or higher-level code) has no idea that the thread was interrupted.
try {lock.tryLock(1L, TimeUnit.SECONDS)
} catch (InterruptedException e) {LOG.info("Interrupted while doing x");
}// Good.
// - Interrupted state is preserved.
try {lock.tryLock(1L, TimeUnit.SECONDS)
} catch (InterruptedException e) {LOG.info("Interrupted while doing x");Thread.currentThread().interrupt();
}
抛出适当的异常类型
让您的API用户服从捕获狭隘的异常,不要抛出异常。即使你正在调用另一个抛出异常的顽皮API,至少要隐藏它,以免它进一步冒泡。在异常方面,您还应该努力隐藏调用方的实现细节。
:::java
// Bad.
// - Caller is forced to catch Exception, trapping many unnecessary types of issues.
interface DataStore {String fetchValue(String key) throws Exception;
}// Better.
// - The interface leaks details about one specific implementation.
interface DataStore {String fetchValue(String key) throws SQLException, UnknownHostException;
}// Good.
// - A custom exception type insulates the user from the implementation.
// - Different implementations aren't forced to abuse irrelevant exception types.
interface DataStore {String fetchValue(String key) throws StorageException;static class StorageException extends Exception {...}
}
使用更新/更好的库
StringBuffer上的StringBuilder
StringBuffer是线程安全的,很少需要。
ScheduledExecutorService over Timer
从Java Concurrency in Practice中提取(直接借用stackoverflow 问题)。
Timer可能对系统时钟的变化很敏感,ScheduledThreadPoolExecutor不是
Timer 只有一个执行线程,因此长时间运行的任务可以延迟其他任务。
ScheduledThreadPoolExecutor可以配置多个线程和a 正确ThreadFactory
查看管理线程
抛出异常抛出TimerTask线程,渲染Timer无效。
ThreadPoolExecutor提供,afterExceute因此您可以显式处理执行结果。
在矢量列表
Vector是同步的,这通常是不需要的。当需要同步时,同步列表 通常可以作为替代列表Vector。
equals()和hashCode()
如果覆盖一个,则必须同时实现这两个。 请参阅equals / hashCode 合约
[Objects.equal()](http://docs.guava-libraries.googlecode.com/git-history/v11.0.2/javadoc/com/google/common/base/Objects.html#equal ( java.lang。 Object,java.lang.Object))和 Objects.hashCode() 使得遵循这些契约变得非常容易。
过早优化是万恶之源。
唐纳德克努特是个聪明人,他对这个话题有几点 要说。
除非您有充分的证据表明需要进行优化,否则通常最好首先实现未优化的版本(可能会留下关于可以进行优化的说明)。
因此,在您花一周时间编写内存映射的压缩霍夫曼编码的哈希映射之前,首先使用库存填充并进行测量。
待办事项
早点和经常离开TODO
TODO并不是一件坏事 - 它向未来的开发人员(可能是你自己)发出了一个考虑因素,但由于各种原因而被忽略了。调试时它也可以作为一个有用的信号。
不保留未分配的TODO
TODO应该拥有所有者,否则他们不太可能得到解决。
:::java
// Bad.
// - TODO is unassigned.
// TODO: Implement request backoff.// Good.
// TODO(George Washington): Implement request backoff.
采用TODO
如果所有者已离开公司/项目,或者您对与TODO主题直接相关的代码进行了修改,则应采用孤儿。
遵守得墨忒耳定律(LoD)
通过违反一点规则,德米特法最明显受到侵犯 ,但还有其他代码结构导致违反法律精神。
在课堂上
拿走你需要的东西,仅此而已。这通常与德克萨斯构造函数有关, 但它也可以隐藏在几乎没有参数的构造函数或方法中。关键的想法是将程序集推迟到知道足以组装的代码层,而只是采用完成工作所需的最小接口。
:::java
// Bad.
// - Weigher uses hosts and port only to immediately construct another object.
class Weigher {private final double defaultInitialRate;Weigher(Iterable<String> hosts, int port, double defaultInitialRate) {this.defaultInitialRate = validateRate(defaultInitialRate);this.weightingService = createWeightingServiceClient(hosts, port);}
}// Good.
class Weigher {private final double defaultInitialRate;Weigher(WeightingService weightingService, double defaultInitialRate) {this.defaultInitialRate = validateRate(defaultInitialRate);this.weightingService = checkNotNull(weightingService);}
}
如果你想以构建器的形式提供一个方便的构造函数,工厂方法或外部工厂,你仍然可以,但是通过使Weigher的基本构造函数只接受它实际使用的东西它变得更容易进行单元测试和适应系统所涉及的。
在方法上
如果一个方法有多个孤立的块,可以考虑通过将这些块提取到只做一件事的辅助方法来命名这些块。除了使呼叫站点看起来更像代码而更像英语之外,提取的站点通常更容易流动 - 分析人眼。经典案例是分支变量赋值。在极端情况下,永远不要这样做:
:::java
void calculate(Subject subject) {double weight;if (useWeightingService(subject)) {try {weight = weightingService.weight(subject.id);} catch (RemoteException e) {throw new LayerSpecificException("Failed to look up weight for " + subject, e)}} else {weight = defaultInitialRate * (1 + onlineLearnedBoost);}// Use weight here for further calculations
}
而是这样做:
:::java
void calculate(Subject subject) {double weight = calculateWeight(subject);// Use weight here for further calculations
}private double calculateWeight(Subject subject) throws LayerSpecificException {if (useWeightingService(subject)) {return fetchSubjectWeight(subject.id)} else {return currentDefaultRate();}
}private double fetchSubjectWeight(long subjectId) {try {return weightingService.weight(subjectId);} catch (RemoteException e) {throw new LayerSpecificException("Failed to look up weight for " + subject, e)}
}private double currentDefaultRate() {defaultInitialRate * (1 + onlineLearnedBoost);
}
通常信任方法的代码阅读器可以执行他们所说的可以快速扫描计算的代码,并且只向下钻取那些我想要了解更多信息的方法。
不要重复自己(干)
有关此主题的更冗长的讨论,请阅读 此处。
只要有意义,就提取常量
在实用程序函数中集中重复逻辑
正确管理线程
在直接或使用线程池生成线程时,您需要特别注意正确管理生命周期。通过阅读Thread的文档,请熟悉守护程序和非守护程序线程的概念(及其对JVM生命周期的影响)。如果不理解这些概念,可能会导致应用程序在关闭时挂起。
正确关闭 ExecutorService是一个稍微棘手的过程(请参阅javadoc)。如果您的代码使用非守护程序线程管理执行程序服务,则需要执行此过程。ExecutorServiceShutdown很好地包含了这种行为。
如果要在VM关闭时自动执行此类清理,请考虑使用ShutdownRegistry注册 。
避免不必要的代码
多余的临时变量。
:::java
// Bad.
// - The variable is immediately returned, and just serves to clutter the code.
List<String> strings = fetchStrings();
return strings;// Good.
return fetchStrings();
不需要的任务。
:::java
// Bad.
// - The null value is never realized.
String value = null;
try {value = "The value is " + parse(foo);
} catch (BadException e) {throw new IllegalStateException(e);
}// Good
String value;
try {value = "The value is " + parse(foo);
} catch (BadException e) {throw new IllegalStateException(e);
}
“快速”实施
不要使用方法的“快速”或“优化”实现来困扰您的API用户。
:::java
int fastAdd(Iterable<Integer> ints);// Why would the caller ever use this when there's a 'fast' add?
int add(Iterable<Integer> ints);