本文通過應(yīng)用中天天都見得到的日志打印談起,聊聊封裝隱藏拌阴,性能優(yōu)化绍绘,惰性求值,消除重復(fù)的技術(shù)實(shí)踐迟赃。
延遲評估
Eliminate Effects Between Unrelated Things.
遠(yuǎn)古時代
這是早期日志打印的方式陪拘。
if (logger.isLoggable(Level.INFO)) {
logger.info("problem:" + getDiagnostic());
}
這個實(shí)現(xiàn)存在如下一些問題:
- 重復(fù)的「樣板代碼」,并且散亂到程序的各個角落捺氢;
- 在
logger.debug
之前藻丢,首先要logger.isLoggable
;Logger
暴露了太多的狀態(tài)邏輯摄乒,違反了LoD(Law of Demeter)
悠反。
應(yīng)用LoD
logger.info("unexpect problem: {}", getDiagnostic());
這樣的設(shè)計雖然將狀態(tài)的查詢進(jìn)行了封裝,但依然存在一個嚴(yán)重的性能問題馍佑。即使日志開關(guān)關(guān)閉斋否,getDiagnostic
都將被調(diào)用;如果它是一個耗時拭荤、昂貴的操作茵臭,將嚴(yán)重地消耗系統(tǒng)性能。
使用Java8
靈活地應(yīng)用Lambda
惰性求值的特性舅世,可以很漂亮地解決這個問題旦委。
public void log(Level level, Supplier<String> supplier) {
if (isLoggable(level)) {
log(supplier.get());
}
}
public void debug(Supplier<String> supplier) {
log(Level.DEBUG, supplier);
}
public void info(Supplier<String> supplier) {
log(Level.INFO, supplier);
}
...
用戶的代碼也更加簡潔,省略了那些重復(fù)的樣板代碼雏亚。
logger.info(() -> "problem:" + getDiagnostic());
使用Scala: call-by-name
使用Java8 Lambda
時缨硝,() ->
的語法顯得有點(diǎn)怪異;如果使用Scala
罢低,可以使用by-name
機(jī)制進(jìn)一步提高表達(dá)力查辩。
def log(level: Level, msg: => String) {
if (isLoggable(level)) {
log(msg)
}
}
def debug(msg: => String) {
log(DEBUG, msg)
}
def info(msg: => String) {
log(INFO, msg)
}
logger.info(s"problem: ${getDiagnostic()}");
s"problem: ${getDiagnostic()}"
語句并非在logger.info
展開計算,它被延遲直至被apply
的時候才真正地被評估和計算。
復(fù)用代碼
DRY: Don't Repeat Youself
用戶空間
再將目光投放到Java
應(yīng)用宜岛,當(dāng)每次要使用Logger
時长踊,都需要搬遷import
語句,并定義logger
的靜態(tài)字段萍倡,這樣的重復(fù)結(jié)構(gòu)很容易通過Copy-Paste
產(chǎn)生身弊。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Application {
private static Logger logger = LoggerFactory.getLogger(Application.class);
public void run() {
logger.trace("start run");
}
}
消除重復(fù)
使用Scala
可以定義Logging
的特質(zhì),以便消除重復(fù)遣铝。
import org.slf4j.{Logger, LoggerFactory}
trait Logging {
val loggerName = this.getClass.getName
lazy val logger: Logger = LoggerFactory.getLogger(loggerName)
def trace(msg: => String) {
if (logger.isTraceEnabled())
logger.trace(msg)
}
def info(msg: => String) {
if (logger.isTraceEnabled())
logger.trace(msg)
}
...
}
混入特質(zhì)
應(yīng)用程序可以通過混入Logging
佑刷,自動得到日志打印的各個接口。
class Main extends App with Logging {
info("starting...")
}