こんにちは。コミュニケーションアプリ「LINE」のモバイルクライアントを開発している石川です。
この記事は、毎週木曜の定期連載 "Weekly Report" 共有の第 3 回です。Weekly Report については、第 1 回の記事を参照してください。
戦略なき戦略
以下の Loggable
は、ログ出力する情報を保持するインターフェースです。
interface Loggable {
val logType: LogType
val logLevel: LogLevel
val logDescription: String
val timestamp: Long
val codeLocation: StackTraceElement
...
)
ここで、どのプロパティをログとして出力するかを決めるために、以下の LogAttribute
という列挙型が必要になったことを想定します。 関数 createLogMessage
は与えられた LogAttribute
を元に、どのプロパティを使ってログメッセージを構築するかを決めます。
enum class LogAttribute { LOG_TYPE, LOG_LEVEL, LOG_DESCRIPTION, ...}
fun createLogMessage(loggable: Loggable, attributesToLog: Set<LogAttribute>): String {
...
}
例えば、 setOf(LOG_LEVEL, LOG_TYPE, LOG_DESCRIPTION)
が attributesToLog
として与えられた場合、ログメッセージ に含まれるプロパティは、ログレベル・種類・説明になることが期待されます。例えば "FATAL, Crash, something wrong with some parameter" のようなメッセージになるでしょう。
さらに、この LogAttribute
の順番は静的に決まっていて、変更する必要はないとします。例えば、「ログレベルはメッセージの最初に位置づけられる」という規則があるとします。この規則を実現するために、開発者は順番を決めるリスト (ORDERED_ATTRIBUTES_TO_LOG
) を定義し、それを使ってログメッセージを構築するかもしれません。
// This order might be different from `ordinal` of `LogAttribute`.
val ORDERED_ATTRIBUTES_TO_LOG: List<LogAttribute> = listOf(
LOG_LEVEL,
LOG_TYPE,
LOG_DESCRIPTION,
...
)
fun createLogMessage(loggable: Loggable, attributesToLog: Set<LogAttribute>): String =
ORDERED_ATTRIBUTES_TO_LOG.asSequence()
.filter(attributesToLog::contains)
.map { attribute ->
when (attribute) {
LogAttribute.LOG_LEVEL -> getLogLevelText(loggable)
LogAttribute.LOG_TYPE -> getLogTypeText(loggable)
...
}
}.joinToString()
このコードの問題点はありますか?
繰り返される「高度な柔軟性」
このコードでは ループの内部に分岐が存在し、かつ、各分岐はループ中に高々 1 回しか使われない という構造になっています。
これにより、以下の 2 つの問題が発生しています。
- 分岐がループの内部に直接書かれているため、関数の流れを把握するために各分岐を把握しなければならない