こんにちは。コミュニケーションアプリ「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 つの問題が発生しています。
- 分岐がループの内部に直接書かれているため、関数の流れを把握するために各分岐を把握しなければならない