KotlinでGradleのタスク結果を効率よくログに記録する方法

Kotlinを使ってGradleビルドを管理する際、タスクの結果や進行状況をログに記録することは、効率的な開発やデバッグにおいて非常に重要です。ビルドが失敗した原因や、タスクが期待通りに動作しているかを素早く確認できるため、プロジェクトのメンテナンスが容易になります。

本記事では、GradleとKotlinを用いてタスクの実行結果をログに記録する方法を解説します。基本的なタスクのログ出力から、成功・失敗の分岐、ログレベルの制御、そして複数タスクのログ管理まで、ステップごとに詳しく説明します。

KotlinとGradleの知識を組み合わせて、ビルドプロセスを見える化し、より効果的な開発環境を構築しましょう。

目次

GradleとKotlinの概要

Gradleは、JavaやKotlinなどのプログラム言語向けのビルド自動化ツールであり、柔軟で高機能なビルドシステムとして広く使われています。Gradleはタスクベースのアプローチを採用しており、ビルド、テスト、パッケージ化などの処理をタスクとして定義して管理します。

Kotlinは、JVM上で動作するモダンなプログラミング言語で、簡潔な構文と高い安全性が特徴です。Gradleのビルドスクリプトは従来、Groovyで書かれていましたが、現在ではKotlinを用いたビルドスクリプト(Kotlin DSL)もサポートされており、型安全で補完機能が強力なため、可読性や保守性が向上します。

GradleとKotlin DSLの利点

GradleのKotlin DSLには以下のような利点があります:

  1. 型安全なスクリプト:Kotlin DSLはコンパイル時に型チェックが行われるため、エラーが早期に発見できます。
  2. IDEサポート:IntelliJ IDEAやAndroid Studioでは、Kotlin DSLによるビルドスクリプトに対して強力な補完機能が提供されます。
  3. 可読性向上:Kotlinのシンプルな構文により、ビルドスクリプトが直感的になります。

Gradleタスクの基本構造

Gradleでは、タスクは以下のように定義されます:

tasks.register("hello") {
    doLast {
        println("Hello, Gradle with Kotlin DSL!")
    }
}

この例では、helloというタスクが作成され、タスクが実行されるとメッセージが出力されます。Kotlin DSLを活用することで、ビルドスクリプトが簡潔かつ効率的になります。

次のセクションでは、Gradleタスクの実行と基本的なログ出力について説明します。

Gradleタスクの実行とログ出力の基本

Gradleではタスクの実行結果を簡単にログに出力することができます。タスクの進行状況や結果をログに記録することで、ビルドのデバッグや問題の特定がスムーズになります。

シンプルなタスクの作成とログ出力

Kotlin DSLを使ってタスクを作成し、ログを出力する基本的な例は以下の通りです。

tasks.register("simpleLogTask") {
    doLast {
        println("This is a simple log message from the task.")
    }
}

このタスクを実行するには、次のコマンドを使用します。

./gradlew simpleLogTask

実行すると、以下のような出力が表示されます。

> Task :simpleLogTask
This is a simple log message from the task.

複数のログメッセージを出力する

タスク内で複数のログメッセージを出力することも可能です。処理の各ステップでメッセージを記録することで、タスクの動作をより詳細に確認できます。

tasks.register("multiStepLogTask") {
    doLast {
        println("Step 1: Task started.")
        println("Step 2: Performing work...")
        println("Step 3: Task completed successfully.")
    }
}

実行結果:

> Task :multiStepLogTask
Step 1: Task started.
Step 2: Performing work...
Step 3: Task completed successfully.

タスクの実行時にパラメータを渡す

Gradleタスクに引数を渡し、動的にログメッセージを変更することもできます。例えば、タスクにカスタムメッセージを渡す方法は以下の通りです。

tasks.register("paramLogTask") {
    val message = project.findProperty("customMessage") ?: "Default message"
    doLast {
        println("Message: $message")
    }
}

このタスクを実行する際に、次のように引数を指定します。

./gradlew paramLogTask -PcustomMessage="Hello from Gradle!"

出力結果:

> Task :paramLogTask
Message: Hello from Gradle!

ログ出力の基本ポイント

  • printlnの使用:簡単なログ出力にはprintlnが便利です。
  • パラメータの利用:柔軟なログ出力を実現するため、プロパティを活用しましょう。
  • ステップごとの出力:処理の進行状況を分かりやすくするために、複数のメッセージを適宜配置しましょう。

次のセクションでは、Kotlinを使ってGradleタスクをカスタマイズし、より高度なログの記録方法を解説します。

KotlinでGradleタスクをカスタマイズする

Gradleでは、Kotlin DSLを使うことでタスクの動作を柔軟にカスタマイズできます。タスクの中で条件分岐や処理の連携、外部入力を受け取るなど、さまざまな要件に対応することが可能です。

タスクの依存関係を設定する

複数のタスク間に依存関係を設定し、特定の順序でタスクを実行する方法を紹介します。

tasks.register("taskA") {
    doLast {
        println("Task A is executed.")
    }
}

tasks.register("taskB") {
    dependsOn("taskA")  // taskBが実行される前にtaskAが実行される
    doLast {
        println("Task B is executed after Task A.")
    }
}

出力結果:

> Task :taskA
Task A is executed.

> Task :taskB
Task B is executed after Task A.

条件分岐を用いたタスクのカスタマイズ

条件に基づいてタスクの動作を変える方法です。例えば、プロパティの値に応じて処理を変更できます。

tasks.register("conditionalTask") {
    doLast {
        val condition = project.findProperty("runExtra")?.toString()?.toBoolean() ?: false
        if (condition) {
            println("Extra condition met. Running additional steps.")
        } else {
            println("Default execution.")
        }
    }
}

実行時にプロパティを渡す場合:

./gradlew conditionalTask -PrunExtra=true

出力結果:

> Task :conditionalTask
Extra condition met. Running additional steps.

プロパティを渡さない場合はデフォルト動作になります。

外部ファイルへのログ出力

タスクの結果を外部ファイルに出力することで、後からログを確認しやすくなります。

import java.io.File

tasks.register("logToFileTask") {
    doLast {
        val logFile = File("build/logs/taskLog.txt")
        logFile.parentFile.mkdirs()
        logFile.writeText("Task executed at: ${java.time.LocalDateTime.now()}\n")
        println("Log has been written to ${logFile.absolutePath}")
    }
}

実行後、build/logs/taskLog.txtにログが記録されます。

複数のアクションをタスクに追加する

1つのタスクに複数の処理ステップ(アクション)を追加することも可能です。

tasks.register("multiActionTask") {
    doFirst {
        println("First action: Preparing task...")
    }
    doLast {
        println("Second action: Task execution complete.")
    }
}

出力結果:

> Task :multiActionTask
First action: Preparing task...
Second action: Task execution complete.

カスタムタスクタイプを作成する

タスクのカスタムタイプを作成し、複数のタスクで再利用する方法です。

abstract class CustomTask : DefaultTask() {
    @TaskAction
    fun perform() {
        println("Executing custom task logic.")
    }
}

tasks.register("customTask", CustomTask::class)

出力結果:

> Task :customTask
Executing custom task logic.

まとめ

Kotlin DSLを使用すると、Gradleタスクを柔軟にカスタマイズできます。依存関係の設定、条件分岐、外部ファイルへのログ記録、複数アクションの追加、カスタムタスクタイプの作成により、プロジェクトのビルドプロセスを効率的に管理しましょう。

次のセクションでは、Gradleが提供するLogging APIを活用して、さらに高度なログ管理を行う方法を解説します。

Gradleの`Logging` APIの活用方法

Gradleには強力なLogging APIが用意されており、タスクの実行状況やビルドプロセスを詳細に記録することができます。Logging APIを使うことで、出力のフォーマット、ログレベルの設定、カスタムメッセージの記録など、柔軟にログ管理が可能です。

基本的な`Logger`の使い方

Gradleタスク内でLoggerを使うには、以下のように記述します。

tasks.register("basicLogTask") {
    doLast {
        logger.lifecycle("Lifecycle: This is a lifecycle log message.")
        logger.info("Info: This is an info log message.")
        logger.debug("Debug: This is a debug log message.")
        logger.warn("Warning: This is a warning log message.")
        logger.error("Error: This is an error log message.")
    }
}

ログレベルの説明

GradleのLoggerでは、以下のログレベルが用意されています。

  • ERROR:重大な問題が発生した場合のログ
  • WARN:警告メッセージ
  • LIFECYCLE:デフォルトの標準出力レベル
  • INFO:詳細な情報メッセージ
  • DEBUG:デバッグ情報(通常は出力されない)

実行時にログレベルを指定する

Gradleのコマンドラインオプションでログレベルを制御できます。

  • --info:INFOレベルのメッセージを表示
  • --debug:DEBUGレベルのメッセージを表示
  • --warn:WARNレベルとそれ以上のメッセージのみ表示
  • --quiet:LIFECYCLEレベルより下のメッセージを非表示

例:INFOレベルでタスクを実行する

./gradlew basicLogTask --info

カスタムロガーの作成

カスタムロガーを作成して、特定の条件でログメッセージを記録することも可能です。

tasks.register("customLoggerTask") {
    doLast {
        val customLogger = logger
        customLogger.lifecycle("Starting custom logger task...")

        if (project.hasProperty("verbose")) {
            customLogger.info("Verbose mode is enabled.")
        } else {
            customLogger.warn("Verbose mode is disabled.")
        }
    }
}

実行時にverboseプロパティを渡す

./gradlew customLoggerTask -Pverbose

ログのフィルタリング

タスクの中で特定の条件に合致するログだけを出力することも可能です。

tasks.register("filterLogTask") {
    doLast {
        val messages = listOf(
            "INFO: Operation completed successfully.",
            "ERROR: File not found.",
            "WARN: Low disk space.",
            "INFO: Task executed."
        )

        messages.filter { it.startsWith("INFO") }
                .forEach { logger.info(it) }
    }
}

ログのフォーマットと出力先の変更

Gradleの設定ファイルbuild.gradle.ktsで、ログのフォーマットや出力先をカスタマイズできます。

logging {
    val fileAppender = java.io.File("build/logs/gradle.log")
    fileAppender.parentFile.mkdirs()
    fileAppender.writeText("Custom log file initialized.\n")
}

まとめ

GradleのLogging APIを活用することで、ビルドやタスクの進行状況を詳細に記録し、デバッグやエラー解決を効率的に行うことができます。ログレベルの調整、カスタムロガー、フィルタリング、出力先のカスタマイズなどを駆使して、プロジェクトのログ管理を最適化しましょう。

次のセクションでは、タスクの成功・失敗に応じたログの分岐方法について解説します。

タスクの成功・失敗のログ分岐

Gradleでは、タスクの成功や失敗に応じて異なるログメッセージを記録することが可能です。これにより、エラー発生時のトラブルシューティングやビルド状態の確認が効率的になります。

タスクの結果をハンドリングする

タスクの成功・失敗に応じた処理を記述するには、doLastdoFirstの中でtry-catchブロックを使用します。以下の例では、タスクが失敗した場合にエラーメッセージを出力します。

tasks.register("conditionalLogTask") {
    doLast {
        try {
            println("Task execution started.")
            // 故意にエラーを発生させる
            throw Exception("Something went wrong!")
            println("Task execution completed successfully.")
        } catch (e: Exception) {
            logger.error("Task failed with error: ${e.message}")
        }
    }
}

実行結果(エラーが発生した場合)

> Task :conditionalLogTask
Task execution started.
Task failed with error: Something went wrong!

タスクの成功・失敗を検知するリスナーの利用

GradleのbuildFinishedリスナーを使用して、ビルド全体の成功・失敗を検知し、ログを記録することもできます。

gradle.buildFinished { result ->
    if (result.failure != null) {
        logger.error("Build failed: ${result.failure?.message}")
    } else {
        logger.lifecycle("Build succeeded!")
    }
}

タスクの状態を条件分岐で確認する

タスクの状態をTaskExecutionListenerで確認し、成功時と失敗時で異なる処理を行う方法です。

gradle.taskGraph.addTaskExecutionListener(object : TaskExecutionListener {
    override fun beforeExecute(task: Task) {
        logger.lifecycle("Starting task: ${task.name}")
    }

    override fun afterExecute(task: Task, state: TaskState) {
        if (state.failure != null) {
            logger.error("Task '${task.name}' failed with error: ${state.failure.message}")
        } else {
            logger.lifecycle("Task '${task.name}' completed successfully.")
        }
    }
})

出力結果(成功時)

Starting task: exampleTask
Task 'exampleTask' completed successfully.

出力結果(失敗時)

Starting task: exampleTask
Task 'exampleTask' failed with error: NullPointerException

複数タスクの成功・失敗を一括でログ管理する

複数のタスクの状態を一括で管理し、成功・失敗に応じたログを記録する場合、以下のように記述します。

tasks.register("task1") {
    doLast {
        println("Executing task 1")
    }
}

tasks.register("task2") {
    doLast {
        println("Executing task 2")
        throw Exception("Error in task 2")
    }
}

gradle.taskGraph.whenReady {
    tasks.forEach { task ->
        task.doLast {
            if (task.state.failure != null) {
                logger.error("Task '${task.name}' failed.")
            } else {
                logger.lifecycle("Task '${task.name}' succeeded.")
            }
        }
    }
}

まとめ

Gradleタスクの成功・失敗に応じたログ管理を行うことで、ビルドプロセスの可視化とエラーのトラブルシューティングが容易になります。try-catch、リスナー、タスクの状態確認を活用して、柔軟にログを記録しましょう。

次のセクションでは、ログレベルの設定と制御方法について解説します。

ログレベルの設定と制御

Gradleでは、タスク実行中に出力するログのレベルを設定・制御することで、必要な情報だけを表示したり、詳細なデバッグ情報を確認したりできます。Kotlin DSLを活用して、ログレベルの設定やカスタマイズ方法を紹介します。

Gradleの主要なログレベル

Gradleには以下の主要なログレベルが用意されています:

  1. ERROR:重大なエラーのみを表示します。
  2. WARN:警告メッセージとエラーを表示します。
  3. QUIET:最低限の出力のみ表示します。
  4. LIFECYCLE:デフォルトのログレベル。ビルドの進行状況を表示します。
  5. INFO:詳細な情報を表示します。
  6. DEBUG:デバッグ情報を含む、最も詳細な出力です。

ログレベルをコマンドラインで指定する

Gradleのタスク実行時に、コマンドラインでログレベルを指定できます。

  • --quiet:QUIETレベルに設定
  • --warn:WARNレベルに設定
  • --info:INFOレベルに設定
  • --debug:DEBUGレベルに設定

例:INFOレベルでタスクを実行する

./gradlew exampleTask --info

例:DEBUGレベルでタスクを実行する

./gradlew exampleTask --debug

タスク内でログレベルを制御する

Kotlin DSLを使い、タスク内で特定のログレベルのメッセージを出力する方法です。

tasks.register("logLevelTask") {
    doLast {
        logger.error("This is an ERROR message.")
        logger.warn("This is a WARN message.")
        logger.lifecycle("This is a LIFECYCLE message.")
        logger.info("This is an INFO message.")
        logger.debug("This is a DEBUG message.")
    }
}

実行時に--infoオプションを付けるとINFOレベルまで表示されます:

./gradlew logLevelTask --info

出力結果:

> Task :logLevelTask
This is an ERROR message.
This is a WARN message.
This is a LIFECYCLE message.
This is an INFO message.

ログレベルを動的に設定する

ビルドスクリプト内で動的にログレベルを変更するには、LoggingManagerを使います。

tasks.register("dynamicLogLevelTask") {
    doLast {
        val previousLevel = logging.level
        logging.level = LogLevel.DEBUG  // ログレベルをDEBUGに変更

        logger.debug("This debug message is now visible.")

        // 元のログレベルに戻す
        logging.level = previousLevel
    }
}

特定のタスクのみログレベルを変更する

特定のタスクだけログレベルを変更するには、doFirstdoLast内で設定します。

tasks.register("specificLogLevelTask") {
    doFirst {
        logger.info("Task is starting...")
        logging.level = LogLevel.DEBUG
    }

    doLast {
        logger.debug("This message will be shown with DEBUG level.")
    }
}

ログ出力をファイルに記録する

Gradleのログをファイルに保存することで、後で詳細なビルド情報を確認できます。

build.gradle.ktsでログ出力を設定する:

logging.addStandardOutputListener { message ->
    java.io.File("build/logs/gradle-output.log").appendText(message + "\n")
}

実行後、build/logs/gradle-output.logにログが記録されます。

まとめ

Gradleのログレベルを適切に設定・制御することで、ビルドの進行状況やデバッグ情報を効率的に確認できます。コマンドラインオプションやKotlin DSLを活用して、必要な情報のみを出力し、ログ管理を最適化しましょう。

次のセクションでは、複数タスクのログを一括管理する方法について解説します。

複数タスクのログを一括管理する方法

Gradleでは、複数のタスクの実行結果やログを一括で管理することが可能です。これにより、プロジェクト全体のビルド状況を把握しやすくなり、問題が発生した際のトラブルシューティングが効率的に行えます。

複数タスクの依存関係を設定する

複数のタスクを連携して実行するには、タスクの依存関係を設定します。

tasks.register("task1") {
    doLast {
        println("Executing Task 1")
    }
}

tasks.register("task2") {
    dependsOn("task1")
    doLast {
        println("Executing Task 2")
    }
}

tasks.register("task3") {
    dependsOn("task2")
    doLast {
        println("Executing Task 3")
    }
}

実行コマンド

./gradlew task3

出力結果

> Task :task1
Executing Task 1

> Task :task2
Executing Task 2

> Task :task3
Executing Task 3

ビルド全体のログを1つのファイルに記録する

複数のタスクのログを一括でファイルに記録する方法です。以下の設定で、ビルド中に出力されたすべてのログをファイルに書き込みます。

build.gradle.kts:

import java.io.File

tasks.register("logToFileTask") {
    val logFile = File("build/logs/build.log")
    doLast {
        logFile.parentFile.mkdirs()
        logFile.writeText("Build started at: ${java.time.LocalDateTime.now()}\n")
    }
}

// ビルド全体のログをキャプチャする設定
gradle.startParameter.logLevel = LogLevel.INFO

gradle.addListener(object : TaskExecutionListener {
    override fun beforeExecute(task: Task) {
        File("build/logs/build.log").appendText("Starting task: ${task.name}\n")
    }

    override fun afterExecute(task: Task, state: TaskState) {
        val result = if (state.failure == null) "SUCCESS" else "FAILED"
        File("build/logs/build.log").appendText("Finished task: ${task.name} with result: $result\n")
    }
})

実行コマンド

./gradlew logToFileTask task1 task2 task3

build/logs/build.logの内容

Build started at: 2024-06-20T12:00:00
Starting task: logToFileTask
Finished task: logToFileTask with result: SUCCESS
Starting task: task1
Finished task: task1 with result: SUCCESS
Starting task: task2
Finished task: task2 with result: SUCCESS
Starting task: task3
Finished task: task3 with result: SUCCESS

タスクごとのログを個別に管理する

各タスクごとに別々のログファイルを作成する方法です。

tasks.register("individualLogTask1") {
    doLast {
        File("build/logs/task1.log").writeText("Task 1 completed successfully.\n")
    }
}

tasks.register("individualLogTask2") {
    doLast {
        File("build/logs/task2.log").writeText("Task 2 completed successfully.\n")
    }
}

実行コマンド

./gradlew individualLogTask1 individualLogTask2

出力ファイル

  • build/logs/task1.log
  Task 1 completed successfully.
  • build/logs/task2.log
  Task 2 completed successfully.

タスクの実行時間をログに記録する

タスクの実行時間をログに記録することで、ビルドのパフォーマンスを分析できます。

tasks.register("timedTask") {
    doLast {
        val startTime = System.currentTimeMillis()
        println("Executing timed task...")
        Thread.sleep(2000)  // 2秒待機
        val endTime = System.currentTimeMillis()
        val duration = endTime - startTime
        println("Task completed in ${duration}ms")
    }
}

実行結果

> Task :timedTask
Executing timed task...
Task completed in 2002ms

まとめ

複数タスクのログを一括で管理することで、ビルドの進行状況や結果を効率よく確認できます。依存関係の設定、ビルド全体のログ出力、タスクごとのログ管理、実行時間の記録など、目的に応じたログ管理手法を活用し、Gradleビルドを最適化しましょう。

次のセクションでは、Kotlinプロジェクトでの応用例について解説します。

実践例:Kotlinプロジェクトでの応用方法

これまで学んだGradleタスクのログ管理テクニックを、実際のKotlinプロジェクトに適用する方法を紹介します。タスクの実行結果を記録し、エラーが発生した際のデバッグやパフォーマンス分析を行う具体例を見ていきます。

プロジェクト構成例

以下は、Kotlinを使ったシンプルなGradleプロジェクトの構成です。

my-kotlin-project/
│── build.gradle.kts
│── settings.gradle.kts
└── src/
    └── main/
        └── kotlin/
            └── Main.kt

1. ビルドタスクでログを記録する

Gradleビルドタスクをカスタマイズして、ビルドプロセスの進行状況やエラーをログに記録します。

build.gradle.kts:

tasks.register("buildProject") {
    doFirst {
        logger.lifecycle("Starting project build...")
    }
    doLast {
        try {
            println("Compiling project...")
            // コンパイル処理をシミュレート
            Thread.sleep(1000)
            logger.lifecycle("Project build completed successfully.")
        } catch (e: Exception) {
            logger.error("Build failed: ${e.message}")
        }
    }
}

実行コマンド

./gradlew buildProject

出力結果

> Task :buildProject
Starting project build...
Compiling project...
Project build completed successfully.

2. タスクの成功・失敗をログファイルに記録する

タスクの成功・失敗に応じたメッセージをログファイルに記録します。

build.gradle.kts:

tasks.register("compileTask") {
    doLast {
        val logFile = java.io.File("build/logs/compile.log")
        try {
            println("Starting compilation...")
            // コンパイル成功をシミュレート
            logFile.writeText("Compilation succeeded at ${java.time.LocalDateTime.now()}\n")
            println("Compilation successful.")
        } catch (e: Exception) {
            logFile.writeText("Compilation failed: ${e.message}\n")
            logger.error("Compilation failed.")
        }
    }
}

実行コマンド

./gradlew compileTask

build/logs/compile.logの内容

Compilation succeeded at 2024-06-20T12:00:00

3. テストタスクのログ記録

Kotlinのユニットテストを実行し、その結果をログに記録します。

build.gradle.kts:

tasks.register("runTests") {
    doLast {
        println("Running tests...")
        try {
            // テスト成功をシミュレート
            logger.lifecycle("All tests passed successfully.")
        } catch (e: Exception) {
            logger.error("Tests failed: ${e.message}")
        }
    }
}

実行コマンド

./gradlew runTests --info

出力結果

> Task :runTests
Running tests...
All tests passed successfully.

4. ビルドとテストのワークフローを一括実行する

ビルド、テスト、ログ記録を一括で実行するタスクを作成します。

build.gradle.kts:

tasks.register("fullBuild") {
    dependsOn("buildProject", "runTests")
    doLast {
        logger.lifecycle("Full build and test workflow completed.")
    }
}

実行コマンド

./gradlew fullBuild

出力結果

> Task :buildProject
Starting project build...
Compiling project...
Project build completed successfully.

> Task :runTests
Running tests...
All tests passed successfully.

> Task :fullBuild
Full build and test workflow completed.

5. パフォーマンス分析のログ記録

タスクの実行時間を計測して、パフォーマンス分析を行います。

build.gradle.kts:

tasks.register("timedBuild") {
    doLast {
        val startTime = System.currentTimeMillis()
        println("Starting timed build...")
        Thread.sleep(2000)  // ビルド処理をシミュレート
        val endTime = System.currentTimeMillis()
        val duration = endTime - startTime
        logger.lifecycle("Build completed in ${duration} ms.")
    }
}

実行結果

> Task :timedBuild
Starting timed build...
Build completed in 2003 ms.

まとめ

これらの応用例を通して、KotlinプロジェクトにおけるGradleタスクのログ管理、成功・失敗のハンドリング、パフォーマンス計測が効率的に行えることが分かります。タスクの連携やログファイルへの出力を活用し、プロジェクトのビルドやテストを効率化しましょう。

次のセクションでは、これまでの内容を簡潔にまとめます。

まとめ

本記事では、Kotlinを用いたGradleタスクのログ記録方法について詳しく解説しました。Gradleの基本的なタスク作成から始め、ログの出力、タスクのカスタマイズ、Logging APIの活用、成功・失敗時のログ分岐、複数タスクの一括管理、そして実際のKotlinプロジェクトへの応用例までをカバーしました。

効率的なログ管理を行うことで、ビルドやテストの進行状況が明確になり、エラー発生時のデバッグやパフォーマンス分析が容易になります。Kotlin DSLとGradleの組み合わせを活用し、開発環境をさらに最適化していきましょう。

今回学んだ内容を実践することで、より信頼性の高いKotlinプロジェクトの運用が可能になります。

コメント

コメントする

目次