PR

DataWeaveとは?10分で速習する完全ガイド

MuleSoft

DataWeave は、MuleSoft(参考 MuleSoftとは?)が提供するデータ変換スクリプト言語です。Mule 4(Anypoint Platformを含む)環境では標準で組み込まれており、JSON、XML、CSV、Javaオブジェクト、さらには複数のフォーマットに対して柔軟かつ便利な変換機能を提供します。

MuleSoftでは、このDataWeaveをうまく活用することで様々な処理を実現することが可能になります。

とはいえ、Dataweaveを集中的に学ぶ機会も限られているのが現実。そこでこのページでは、Dataweaveとは何か?から始め、Dataweaveを利用した簡単なコーディング(少なくともコードを読むことができる)が可能になるようにDataweaveの構文ルールを1からわかりやすく解説します。

スポンサーリンク

DataWeaveの特徴

まずは、簡単にDataWeaveの特徴をお示しします。

  1. 宣言的(Declarative)なスタイル
    従来の命令型スタイル(if/for/whileなど逐次処理)とは異なり、「入力データをどのように出力データへマッピングするか」を宣言的に書くことで、表現力豊かなデータ変換を少ないコード量で実現します。
  2. 各種データ形式に対応
    JSON、XML、CSV、Javaオブジェクト、YAMLなど、多様な形式との相互変換に対応します。Mule Flow内ではtransformコンポーネントでDataWeaveスクリプトを記述し、入力から出力への変換を定義します。
  3. 関数型言語としての特性
    DataWeaveは変換を行うために関数型言語の要素を取り入れており、副作用が少なくテスト可能で読みやすいコードを書くのに適しています。
  4. Mule 4以降でメイン言語として利用
    Mule 3まではMule Expression Language (MEL)とDataWeave 1.0の2本立てでしたが、Mule 4以降はDataWeave 2.0がメイン。大幅に強化された機能が用意されています。
Q
Transformコンポーネントとは・・・?
A

MuleSoftのフローでデータ変換を実行するためのコンポーネントです。主に以下の役割を果たします。

  1. データフォーマットの変換
    JSON、XML、CSV、Javaオブジェクトなど、異なる形式間の変換を行います。
  2. DataWeaveスクリプトの実行
    Transformコンポーネント内でDataWeaveを使い、データを柔軟に加工・整形します。
  3. 入力と出力のマッピング
    フロー内のペイロード(payload)や属性(attributes)を操作し、次の処理へ渡すデータを定義します。

DataWeaveの基本構文

DataWeaveスクリプトの基本構造

DataWeaveスクリプトは大きく以下の3つのセクションで構成されます。

%dw 2.0
output application/json
---
payload
  1. 宣言セクション(Directive)
    %%dw 2.0のようにバージョンを示します。また必要に応じてimport等(※後述)を記述します。
  2. 出力形式(Output)の指定
    output application/jsonなど(※後述)で、変換結果のメディアタイプを指定します。
  3. 変換式(Body)本体
    ---の後に変換処理を記述します。ここに式(式言語)を書いていきます。

サンプルコード 最もシンプルな例

%dw 2.0
output application/json
---
payload

上記の場合、入力(payload)がそのままJSONとして出力されます。

Dataweaveのバージョンについて

DataWeave 1.0:

  • Mule 3で導入されたバージョン。
  • 構文が異なる: 1.0では少し冗長で、2.0ほどの柔軟性がありません。
  • output application/json --- などのセクション構成は似ていますが、詳細な書き方に差異があります。

DataWeave 2.0:

  • Mule 4で採用。現在の主要バージョン。
  • 機能強化: より宣言的なスタイル、追加の高階関数(flatMapreduceなど)、モジュールの柔軟な利用。
  • JSON、XML、CSV、Javaオブジェクトなどのフォーマット間変換が簡易化

Mule 4を使用する場合、DataWeave 2.0がデフォルトで使われます。1.0はMule 3専用のため、2.0を学習・利用するのが推奨されます。

Script Header の各種オプション

  • output application/json skipNullOn="everywhere"
    Nullを出力から除外するといった細かいオプション指定が可能です。
  • output application/xml indent="true"
    XML出力を整形するなど、各種メディアタイプに応じたオプション設定ができます。

コメント

  • 行コメント: // ここはコメント
  • ブロックコメント: /* ここはブロックコメント */
%dw 2.0
output application/json
---
/* 
  ブロックコメント: 
  以下では、サンプルデータの操作と
  コメントの使い方を示します。
*/

var sampleData = [1, 2, 3, 4, 5] // 行コメント: 配列データを定義

{
    // 配列の要素を2倍にする
    doubledValues: sampleData map (item) -> item * 2,
    
    /* 条件に基づくフィルタリング:
       2倍した値が6以上の場合のみ取得 */
    filteredValues: sampleData
        map (item) -> item * 2 // 要素を2倍
        filter (item) -> item >= 6, // フィルタリング
    
    // 結果の概要
    summary: {
        originalSize: sizeOf(sampleData), // 元のデータの要素数
        filteredSize: sizeOf(sampleData filter (item) -> item * 2 >= 6) // フィルタリング後の要素数
    }
}

データ型とリテラル

DataWeaveで扱える代表的な型・リテラルは以下の通りです。

スカラー型

Q
スカラー型とは?
A

単一の値を表すデータ型のこと。複雑な構造を持つ配列やオブジェクトとは異なり、最小単位のデータ型と考えればOK。

%dw 2.0
output application/json
---
{
    stringExample: "Hello, DataWeave", // String
    numberExample: 123,               // Number
    booleanExample: true,             // Boolean
    nullExample: null,                // Null
    dateExample: |2023-01-01|         // Date
}
スカラー型説明サンプル
String文字列型"Hello"
Number数値型(整数・小数)42, 3.14
Boolean真偽値型(論理型)true, false
Null値が存在しないことを示す型null
Date日付型(日時を含む場合もあり)2023-01-01

ポイント DataWeaveの型システムは動的型付け

DataWeaveは、データ型を明示的に宣言する必要がない動的型付けの仕組みを持っています。つまり、変数やデータの型は、DataWeaveが自動的に解釈して適切な操作を適用します。

%dw 2.0
output application/json
---
{
    inferredString: "Hello",    // Stringとして認識
    inferredNumber: 123,       // Numberとして認識
    inferredBoolean: true,     // Booleanとして認識
    inferredNull: null         // Nullとして認識
}

ポイント 必要に応じて明示的に型を指定

%dw 2.0
output application/json
---
{
    explicitNumber: "123" as Number, // StringをNumberに変換
    explicitString: 456 as String   // NumberをStringに変換
}

as 演算子で型変換を行います。DataWeaveでは型変換が明示的に行えるため、操作の意図が明確になります。

配列 (Array)

  • 配列は[ ]で表します。["apple", "banana", "orange"]
  • ネストする場合:[1, 2, ["nested", "array"], 4]
%dw 2.0
output application/json
---
{
    // 元の配列
    originalArray: [1, 2, 3, 4, 5], 
    // 結果: [1, 2, 3, 4, 5]

    // 配列の各要素を2倍に変換
    doubledValues: [1, 2, 3, 4, 5] map $ * 2, 
    // 結果: [2, 4, 6, 8, 10]

    // 偶数のみを抽出
    evenNumbers: [1, 2, 3, 4, 5] filter $ mod 2 == 0, 
    // 結果: [2, 4]

    // 最初の3つの要素を取得
    firstThree: [1, 2, 3, 4, 5][0 to 2], 
    // 結果: [1, 2, 3]

    // 配列内で各要素に対してインデックスも使用して新しい形式に変換
    indexedValues: [10, 20, 30] map (value, index) -> {
        index: index,
        value: value
    }
    // 結果: [{"index": 0, "value": 10}, {"index": 1, "value": 20}, {"index": 2, "value": 30}]
}

オブジェクト (Object)

DataWeaveのオブジェクトは、キーと値のペアで構成されるデータ構造です。本質的には、JSONのオブジェクトと同じ概念で、データを構造化して整理するために使われます。

  • オブジェクトは { } で表し、キーと値をペアで記述。
{
    "name": "Taro",
    "age": 20
}

キーに変数などを利用したい場合はダブルクォートやバッククォートを使わずに直接書くこともできます(ただし特殊文字を含む場合はクォート必須)。

オブジェクトの値にはスカラー型、配列、他のオブジェクトをネスト可能です。

{
    user: { name: "Taro", age: 25 },
    roles: ["admin", "editor"]
}

Date/Time型

  • DataWeaveには日時型リテラルがあります。
    • 例: |2023-01-01| はDate、 |2023-01-01T12:34:56|はDateTimeなど
  • 内部的にはjava.timeのAPIを活用して処理されます。
%dw 2.0
output application/json
---
{
    // 現在の日付と時刻
    currentDateTime: now(),

    // 現在の日付のみ
    currentDate: now() as Date,

    // 現在時刻をフォーマット (例: yyyy/MM/dd HH:mm:ss)
    formattedDateTime: now() as String {format: "yyyy/MM/dd HH:mm:ss"},

    // 日付文字列をDate型に変換
    stringToDate: "2025-01-21" as Date,

    // Date型に日数を加算
    datePlus5Days: |2025-01-21| + |P5D|, // 5日を加算 (P5Dは期間のISO 8601表記)

    // DateTime型の差分計算
    daysBetween: daysBetween(|2025-01-21|, |2025-01-26|)
}

変数と命名

変数宣言

DataWeaveでは、varキーワードを使用して変数を宣言し、後で使用することができます。スクリプトのHeaderか、Bodyの任意の位置で宣言できますが、スコープに注意しましょう。

%dw 2.0
output application/json
---
var greeting = "Hello"
var year = 2025
{
    message: greeting ++ " DataWeave!",
    currentYear: year
}

// ++ は文字列の連結演算子 //

letブロック内での変数宣言

スクリプトの中で部分的な計算に利用する変数を宣言するときにはletブロックを使います。

%dw 2.0
output application/json
---
{
    result: do {
        var x = 10
        var y = 20
        ---
        x + y
    }
}

do { ... }ブロック(旧称doブロック)内でvarを使って変数を定義し、ブロックの最後の式を返り値として利用できます。

関数

関数の定義

DataWeaveではfunキーワードを使用して関数を定義できます。関数は基本的に不変(Immutable)な引数をとり、副作用がない形式で記述が可能です。

fun 関数名 (引数1: 型, 引数2: 型 = デフォルト値, ...) : 戻り値の型 = 式
  1. fun: 関数を定義するキーワード。
  2. 関数名: 任意の識別子(アルファベット、数字、アンダースコアで構成)。
  3. 引数:
    • 引数名と型を指定(例: x: Number)。
    • 必要に応じてデフォルト値を設定可能(例: y: Number = 10)。
  4. 戻り値の型(省略可能):
    • 関数が返すデータの型を指定。
  5. :
    • 関数のロジックを記述。最後の評価結果が返り値となる。
%dw 2.0
output application/json
---
fun add(a, b) = a + b
fun greet(name) = "Hello " ++ name

{
    sum: add(5, 7),
    greeting: greet("DataWeave")
}

ポイント: 関数は式として利用でき、変数へ格納したり、ほかの関数に渡したりできます。

引数の型指定

DataWeave 2.0 では任意で引数や戻り値に型を指定できます。指定しない場合は動的に解釈されます。

fun add(a: Number, b: Number): Number = a + b

ローカル関数とグローバル関数

  • スクリプトヘッダに宣言するとグローバル関数として使用できます。
  • do { }ブロック内で宣言すれば、スコープ内限定のローカル関数として使用可能です。

条件分岐とパターンマッチ

if ... else

DataWeaveでも一般的な条件分岐としてif ... elseが使えます。

if 条件式 
    then 真の場合の値 
    else 偽の場合の値
  1. if: 条件式の開始キーワード。
  2. 条件式: true または false を返す式を記述。例: x > 10
  3. then: 条件がtrueのときに評価される値。
  4. else: 条件がfalseのときに評価される値。
%dw 2.0
output application/json
---
fun checkNumber(n) =
    if (n > 0) "positive"
    else if (n < 0) "negative"
    else "zero"

{
    result1: checkNumber(10),
    result2: checkNumber(-5),
    result3: checkNumber(0)
}
%dw 2.0
output application/json
---
{
    ageCategory: 
        if payload.age >= 18 
            then "Adult"
            else "Child"
}
// 入力例: {"age": 20} -> 出力: {"ageCategory": "Adult"}
// 入力例: {"age": 15} -> 出力: {"ageCategory": "Child"}

match演算子(パターンマッチ)

DataWeaveにはパターンマッチングを使う機能があります。これを使うと、値や型に応じた複雑な分岐をシンプルに書くことができます。

value match {
    case 条件1 -> 処理1,
    case 条件2 -> 処理2,
    ...
    else -> デフォルト処理
}
  1. value: マッチ対象の値。
  2. case 条件 -> 処理: 条件に一致した場合の処理。
  3. else -> 処理: どの条件にも一致しない場合の処理(オプション)。
%dw 2.0
output application/json
---
fun matchType(value) = 
    value match {
        case is Number -> "これは数字"
        case is String -> "これは文字列"
        case is Array -> "これは配列"
        else -> "それ以外"
    }

{
    testNumber: matchType(42),
    testString: matchType("Hello"),
    testArray: matchType([1,2,3]),
    testOther: matchType({ key: "value" })
}
  • case is Numberのように型判定が可能です。
  • 値そのものをパターンとして指定することもできます。
%dw 2.0
output application/json
---
fun matchValue(value) =
    value match {
        case 0 -> "ゼロです",
        case 1 -> "ワンです",
        case -1 -> "マイナスワンです",
        else -> "その他の値です"
    }

{
    result1: matchValue(0),
    result2: matchValue(1),
    result3: matchValue(-1),
    result4: matchValue(5)
}

イテレーションと高階関数

高階関数は「関数を操作する関数」であり、配列やオブジェクトのようなデータ構造を柔軟に操作するための重要なツールです。DataWeaveのデータ変換でよく利用されるので、特にmapfilterのような典型例を理解しておくと便利です!

map

配列あるいはオブジェクトに対して要素ごとに処理を行い、変換した結果を返すのがmapです。

array map (item, index) -> 式

/*
[1, 2, 3] map (item, index) -> item * index
// 結果: [0, 2, 6]
*/

配列の例

%dw 2.0
output application/json
---
["apple", "banana", "orange"]
    // 各要素に対して処理
    map (item, index) -> {
        fruit: item,
        index: index
    }

map (item, index) -> ... の形で、要素とインデックスを引数として受け取れます。

オブジェクトの例

%dw 2.0
output application/json
---
{
    a: 1,
    b: 2,
    c: 3
}
    mapObject (value, key) -> (key ++ "_value") : value * 2

mapObject (value, key) -> ... を使うと、オブジェクトを別の形に変換できます。上記例ではキーを変えて値を倍にする処理をしています。

filter

配列やオブジェクトの要素を条件に基づいて絞り込み(フィルタリング)します。

array filter (item, index) -> 条件式
  • array: 操作対象の配列。
  • item: 現在の要素。
  • index: 現在の要素のインデックス(省略可能)。
  • 条件式: 各要素を絞り込むための条件(trueを返す要素が結果に含まれる)。
%dw 2.0
output application/json
---
[1, 2, 3, 4, 5]
    filter (item) -> item % 2 == 1

// 上記は奇数だけをフィルタリングします。//

reduce

配列の要素を1つに集約(畳み込み)するときに用います。初期値と畳み込みロジックを指定します。

%dw 2.0
output application/json
---
[1, 2, 3, 4, 5]
    reduce (item, accumulator = 0) -> accumulator + item

// 例では配列の合計値を求めています。//

その他の高階関数

  • pluck: オブジェクトからキー・値・インデックス(またはキー)を取り出して配列に変換
  • flatMap: map後の配列をフラットに結合
  • distinctBy: ユニークな要素抽出

など多数の高階関数が用意されています。

主要な演算子・機能

オブジェクトのスプレッド・結合

DataWeaveではオブジェクトのマージや配列の連結を簡単に行えます。

オブジェクトの結合

%dw 2.0
output application/json
---
var person1 = { name: "Taro", age: 20 }
var person2 = { age: 25, city: "Tokyo" }
---
person1 ++ person2


/*
{
  "name": "Taro",
  "age": 25,
  "city": "Tokyo"
}
*/

同じキーがある場合は右側が優先されます(例:ageが25になる)。

配列の結合

%dw 2.0
output application/json
---
[1, 2, 3] ++ [4, 5, 6]

// 配列の場合は単純に連結します: [1,2,3,4,5,6] //

オブジェクトの破壊的パターン(Destructuring)

オブジェクトのキーを展開してローカル変数として取り出すことができます。

%dw 2.0
output application/json
---
fun getPersonInfo(person) = 
    // { name, age, city = "Unknown" } でpersonのキーを抜き出す
    person match {
        case { name, age, city = "Unknown" } -> 
            "Name: " ++ name ++ ", Age: " ++ age ++ ", City: " ++ city
        else -> "No match"
    }

getPersonInfo({ name: "Hanako", age: 23 })
  • city = "Unknown" は、キーcityが存在しない場合にデフォルト値として"Unknown"を割り当てる書き方です。

when ... otherwiseによる条件生成

オブジェクトのフィールドを生成するときに条件分岐を使う場合、when ... otherwiseを活用するとスッキリ書けます。

%dw 2.0
output application/json
---
{
    discount: (payload.price > 1000) when true else 0,
    category: "premium" when (payload.price > 500) else "standard"
}

各種フォーマットの入出力

JSONの読み書き

  • 読み取り: input application/json でペイロードをJSONとして解釈。
  • 書き出し: output application/json で結果をJSONとして出力。
%dw 2.0
output application/json
---
payload // JSON形式の入力をそのまま出力

XMLの読み書き

  • 読み取り: input application/xml
  • 書き出し: output application/xml

XML入力例

<book>
  <title>DataWeave in Action</title>
  <author>John Doe</author>
</book>

DataWeaveで扱うとオブジェクトに変換されます。

{
  "book": {
    "title": "DataWeave in Action",
    "author": "John Doe"
  }
}

XML出力例

%dw 2.0
output application/xml
---
{
    book: {
        title: "DataWeave in Action",
        author: "John Doe"
    }
}

bookがルート要素となります。

CSVの読み書き

  • 読み取り: input text/csv separator=","
  • 書き出し: output text/csv

CSV入力例

name,age,city
Taro,20,Tokyo
Hanako,25,Osaka

DataWeaveで扱うと配列のオブジェクトとして解釈されます:

[
  { "name": "Taro", "age": "20", "city": "Tokyo" },
  { "name": "Hanako", "age": "25", "city": "Osaka" }
]

CSV出力例

%dw 2.0
output text/csv
---
[
    { name: "Taro", age: 20, city: "Tokyo" },
    { name: "Hanako", age: 25, city: "Osaka" }
]

これが下記のようなCSVになる:

name,age,city
Taro,20,Tokyo
Hanako,25,Osaka

その他の形式

  • YAML なども同様に扱えます。( input application/yaml, output application/yaml )
  • Java オブジェクト (上級者向け): Mule Flows でJavaコンポーネントなどを使う場合に、DataWeaveでJavaオブジェクトに直接アクセスすることもできます。

モジュールと拡張機能

モジュールのインポート

DataWeave 2.0 では import 構文を使用して、外部のモジュールやユーザー定義モジュールを読み込めます。

%dw 2.0
import myModule from modules::myModule
output application/json
---
myModule.someFunction(payload)
  • Muleプロジェクト内でsrc/main/resources/modules/myModule.dwlのようなパスにモジュールを定義しておけばインポートできます。

関数のエイリアス

%dw 2.0
import upperCase as uc from dw::core::Strings
output application/json
---
uc("hello world")
  • 公式モジュールdw::core::Stringsなどもよく活用します。

有名な組み込みモジュール例

  • dw::core::Strings:文字列操作(upperCase, lowerCase, split, etc.)
  • dw::core::Dates:日時操作(now(), toString(), etc.)
  • dw::core::Numbers:数値操作(round, floor, ceiling, etc.)
  • dw::core::Binaries:バイナリデータ操作
  • dw::core::Crypto:暗号化ハッシュや署名

DataWeave 2.0でのよく使う式の実例

JSON -> JSONの変換例

入力(JSON)

{
  "users": [
    { "name": "Taro", "age": 20 },
    { "name": "Hanako", "age": 25 }
  ],
  "admin": "Yamada"
}

出力(JSON)に変換する例

%dw 2.0
output application/json
---
{
    administrator: payload.admin,
    userCount: sizeOf(payload.users),
    userNames: payload.users map (u) -> u.name
}

結果:

{
    "administrator": "Yamada",
    "userCount": 2,
    "userNames": ["Taro", "Hanako"]
}

JSON -> CSVの変換例

入力(JSON)

[
    { "name": "Taro", "age": 20, "city": "Tokyo" },
    { "name": "Hanako", "age": 25, "city": "Osaka" }
]

変換スクリプト

%dw 2.0
output text/csv quoteValues=false
---
payload

結果(CSV):

name,age,city
Taro,20,Tokyo
Hanako,25,Osaka

XML -> JSONの変換例

入力(XML)

<departments>
  <department>
    <id>1</id>
    <name>Sales</name>
  </department>
  <department>
    <id>2</id>
    <name>Engineering</name>
  </department>
</departments>

DataWeave

%dw 2.0
output application/json
---
payload.departments.department map (dept) -> {
    departmentId: dept.id,
    departmentName: dept.name
}

結果(JSON):

[
  {
    "departmentId": "1",
    "departmentName": "Sales"
  },
  {
    "departmentId": "2",
    "departmentName": "Engineering"
  }
]

名前空間 (Namespace) の扱い (主にXML)

XMLで名前空間がある場合、DataWeaveではキーに ns0::element の形式を用います。必要に応じてHeaderでxmlnsを定義できます。

%dw 2.0
output application/json
ns ns0 urn:example
---
payload.ns0::Root.ns0::Child

変換フロー内でのattributesの利用

MuleのFlowでファイルを読み込んだ場合など、メッセージのメタデータはattributesに格納されます。DataWeaveスクリプト内ではattributesにアクセスしてヘッダ情報やファイル名などを参照できます。

%dw 2.0
output application/json
---
{
    fileName: attributes.fileName,
    fileSize: attributes.fileSize,
    content: payload
}

スクリプトのモジュール化・再利用

大規模プロジェクトでは、複数のDataWeaveスクリプトをモジュール化して整理すると管理が楽になります。

  • myModule.dwl を作り、共通の関数を定義
  • 他のDataWeaveスクリプトで import して利用

関数型プログラミングの活用

DataWeaveは変数の再代入ができないため、副作用が少ない関数型スタイルでコードを整理できます。

  • 無名関数を多用
  • 変数に依存せず引数→戻り値のみでロジックを記述

ストリーミング

大容量データを扱う場合、DataWeaveはストリーミングモードでデータを逐次処理できます。<all>に対応するような設定をすると、全件読み込まずに処理するなどの最適化が可能です(ただし処理内容によってはバッファリングが入るので要注意)。

ベストプラクティス

  1. マッピングの見やすさを優先
    変換ロジックが可読性を保つように、mapObject などを適切に使い、フラットに書きすぎない。
  2. 関数の分割・再利用
    1つの巨大スクリプトにまとめるのではなく、意味ごとに関数を分割したり、別ファイルに切り出す。
  3. Null処理を明示的に
    Nullを許容するか、default値を設定するかを早期に決める。skipNullOn="everywhere" などのオプションも活用。
  4. 型を意識する
    特にCSVやXMLなどの入出力時は文字列になりやすいので、必要ならtoNumber()等で型変換する。
    • 例: payload.age as Number
  5. サイズの大きいデータはストリーミングやchunkを検討
    大量データを扱う場合、メモリやパフォーマンスに注意。DataWeaveのストリーミング機能を使い、可能な限り流し込み変換できるように工夫する。
  6. エラーハンドリングを考慮
    変換処理中に異常データが来る場合、例外ハンドリングやtryモジュールを組み合わせる。
  7. テスト可能性
    MUnitなどのテスティングフレームワークを使ってDataWeaveスクリプトを単体テストする。関数型の構造を取るとテストしやすい。

このガイドでは、DataWeaveを扱うための基本概念から、構文、主要な関数、高度なモジュール活用例、ベストプラクティスまで幅広く紹介しました。DataWeaveはMuleSoftの強力なデータ変換エンジンであり、宣言的なスタイルと豊富なビルトイン関数を活用することで、複雑なデータマッピングを簡潔に表現できます。

実際の現場で扱うデータフォーマットは多岐にわたり、要件も多様です。しかしDataWeaveの表現力と拡張性を駆使すれば、JSON, XML, CSV, YAML, Javaオブジェクトなどの複数フォーマット間の変換を効率よく行えます。

ぜひ今回のガイドを参考にしながら、ご自身のプロジェクトでDataWeaveを活用し、実践的なスクリプトを書いてみてください。「書いて試す」ことが習熟への近道です。

タイトルとURLをコピーしました