Power Queryライブラリを変換契約として設計する

LWP | Power Queryライブラリを変換契約として設計する

LWP TECHNICAL ARTICLE | 182

Power Queryライブラリを変換契約として設計する

TakiLibの実装から、公開面・責務・依存方向・エラー契約を読み解く

Copyright © 2026 LWP 山中 一弘

本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。

記事要約

Power Queryのライブラリは、便利な関数を一か所へ集めただけでは長く使えません。設計すべきなのは、公開する名前、入力と戻り値、関数ごとの責務、関数をつなぐ依存方向、そして異常時の既定動作です。

この記事では、Power Query Mライブラリ TLib のバージョン1.0.41を実例として扱います。TLibは、一つのrecordから19個の公開関数、メタデータ、READMEを参照できる構造を持ちます。UtilityとComponentを分け、ワークブック取得、リソース選択、データ変換の責務を段階化し、欠損や重複を黙って補正しない設計です。

結論は、Power Queryライブラリとは「関数集」ではなく、「何を受け取り、何を返し、何をせず、どの異常で止まるか」を名前付きで公開する変換契約だということです。

本記事の対象とゴール

想定読者

  • Power Queryのカスタム関数が増え、整理方法を考えている方

  • 共通処理を案件ごとのクエリから分離したい方

  • 汎用化、エラー処理、API命名をどこまで行うか判断したい方

本記事で得られること

  1. recordを小さな名前空間として使うライブラリ構造を理解できます。

  2. 取得・選択・変換を分け、依存方向を一本にする基準を持てます。

  3. 厳格な既定動作と必要十分な抽象化を、実務の契約として設計できます。

はじめに 同じ処理を関数にしただけではライブラリではない

同じMコードを二度書いたら、関数へ切り出す価値があります。しかし関数が10個、20個へ増えたとき、利用者は別の問題に直面します。

  • どの名前を呼べばよいのか

  • 何を渡せばよいのか

  • 何が返るのか

  • 0件や重複が起きたらどうなるのか

  • どこまで自動加工されるのか

  • どの関数と組み合わせるのか

これらへ答えられなければ、共通関数の置き場はできても、予測可能なライブラリにはなっていません。

本記事では、ローカル正本 C:\Dropbox\takilib\taki3lib4PQ.txt を2026年8月21日に読み戻した結果を基に、設計原則を整理します。対象スナップショットは次のとおりです。

項目 確認値
ライブラリ名 TLib
バージョン 1.0.41
ライブラリ更新日時 2026-08-15 23:05:42
行数 1,505行
公開関数 19個
SHA-256 F3D48499611A7E85266E8A21EF54AB13BA4B927C600B60FDDEAF52FC1C4AE53D

第1章 公開面を一つのrecordへまとめる

1.1 関数はrecordのフィールドにできる

Mでは関数も値です。そのため、recordの各フィールドへ関数値を置けます。

[
    LIB_NAME = "TLib",
    LIB_VERSION = "1.0.41",
    value_is_blank =
        (a_value as any) as logical =>
            a_value = null,
    table_lookup =
        (a_table as table, a_lookup as record) as record =>
            ...
]

このrecordを TLib というクエリへ登録すると、利用側はフィールドアクセスで公開メンバーを参照できます。

let
    table_lookup = TLib[table_lookup],
    Item = table_lookup(Source, [Code = 1001])
in
    Item

1.2 recordが小さな名前空間になる

一つのrecordへ公開面をまとめる利点は、次のとおりです。

  • ライブラリ全体を1クエリとして配布できる

  • TLib[table_lookup] の形で出所が明確になる

  • 関数とバージョン、更新日時、READMEを同じ入口から参照できる

  • Record.FieldNames(TLib) などで公開内容を調査できる

  • 利用側は必要な関数だけをローカル名へ取り込める

ここでrecordを巨大な状態コンテナにはしません。TLibが公開するのは関数、メタデータ、説明情報であり、各処理で変わる業務データを抱え込む仕組みではありません。

1.3 公開面を深くしすぎない

次のような階層は分類として美しく見えます。

TLib[Table][Lookup](...)

しかし階層が深くなるほど、呼び出し、検索、既存コードとの互換性が複雑になります。TLibはUtilityとComponentを概念上分けながら、公開名は TLib[table_lookup] のように平坦に保っています。

分類はコード配置と文書で示し、日常の呼び出し構文は短くする。この折り合いは、小規模から中規模の実務ライブラリに向いています。

第2章 便利な入口と組み合わせる部品を分ける

2.1 Utilityは狭い用途を短くする

TLibのUtilityには、頻出する具体的な操作があります。たとえば defined_name_read は、現在のExcelブックにある名前付き項目を読み、1行1列の場合だけ値を返します。

TaxRate = TLib[defined_name_read]("TaxRate")

この関数は便利ですが、外部ブックの取得、複数セルの自動処理、型の暗黙変換まで引き受けません。用途を狭く固定するから、短く安全に使えます。

2.2 Componentは組み替えられる部品にする

より一般的な処理は、責務ごとの関数を組み合わせます。

let
    workbook_from = TLib[workbook_from],
    table_from = TLib[table_from],
    CurrentBook = workbook_from(),
    SettingsTable = table_from(CurrentBook, [Name = "環境設定"])
in
    SettingsTable

ここでは、ワークブック資源の一覧を作る処理と、一覧から対象を解決する処理が分かれています。

2.3 層を増やすこと自体を目的にしない

他言語の設計用語を持ち込み、Utility、Service、Repository、Provider、Adapterと層を増やしても、Mコードが読みやすくなるとは限りません。

層分けの目的は、利用者が「頻出操作の短い入口」と「組み合わせ可能な部品」を見分けられることです。table、list、record、functionの変換関係を追える最小限の層に留めます。

第3章 取得・選択・変換の依存方向を一本にする

3.1 下流ほど小さな値を受け取る

TLibのワークブック処理は、概念的に次の方向でつながります。

binary / null
↓
workbook_from
↓
ワークブック資源の一覧table
↓
table_from
↓
解決済みdata table
├→ env_read
├→ table_lookup
└→ columns_* 系

責務は三段階です。

  • workbook_from がデータソースから資源一覧を作る

  • table_from が一覧から対象資源を1件に解決する

  • 下流の関数が解決済みtableを変換する

すべての関数へ巨大なcontext recordを渡さず、各関数が必要とする最小の値を渡します。

3.2 外部ソースの差は境界で正規化する

Excel.CurrentWorkbook() と Excel.Workbook() は同じ形を返しません。workbook_from は、両者を Name、Kind、Data、Hidden の4列を持つコンテキストへ正規化します。

ただし、この段階でヘッダー昇格や型変換までは行いません。ソースの違いを吸収する責務と、業務データを加工する責務を混ぜないためです。

3.3 外部状態へのアクセスを境界へ閉じる

下流の env_read や table_lookup が内部で毎回 Excel.CurrentWorkbook() を呼ぶと、どのデータを読んでいるのか分かりにくくなります。小さなテストデータだけを渡す検証も難しくなります。

取得を上流へ閉じ、下流は引数で渡されたtableだけを見るようにします。これにより、再利用性とテスト可能性が上がります。

第4章 1関数1責務と「自動化しない」設計

4.1 毎回行う処理と常に行ってよい処理は違う

table_from は対象資源のDataを返しますが、次を自動実行しません。

  • ヘッダー昇格

  • 型変換

  • エラー置換

  • Table.Buffer

Sheet、Excelテーブル、名前定義では、Dataの意味が異なります。常にヘッダーを昇格すれば、正しい1行目を失う場合があります。

「利用側でよく行う」ことは、「低層ライブラリが無条件に行ってよい」ことを意味しません。

4.2 bufferを性能改善の既定値にしない

Table.Buffer や Binary.Buffer は、状況によっては評価の重複を抑えます。一方で、query foldingを妨げたり、メモリ消費を増やしたりする場合があります。

低層関数が自動的にbufferするのではなく、データ量、再評価、foldingを判断できる利用側が明示します。

4.3 戻り値の意味が違うなら関数を分ける

TLibには、セルエラーを固定値へ置換する table_error_replace と、エラー内容を説明文字列へ変換する table_error_describe があります。

固定置換と内容説明では、戻り値の意味が異なります。万能なoptionsで実行時に切り替えるより、関数名と契約を分けた方が呼び出しを読めます。

第5章 標準M関数の薄いwrapperに留める

ライブラリ化すると、組み込み関数をすべて独自名で包みたくなります。しかしwrapperが標準関数の引数順やエラー挙動を変えると、利用者のM知識を使えなくします。

たとえば table_error_replace は、Table.ReplaceErrorValues へ委譲する薄い入口です。

(a_table as table, a_replacements as list) as table =>
    Table.ReplaceErrorValues(a_table, a_replacements)

薄いwrapperの価値は、標準関数を隠すことではありません。

  • ライブラリの命名体系へ入口をそろえる

  • 用途をドキュメント化する

  • 利用箇所を検索しやすくする

  • 将来の変更境界を固定する

これらの価値がないwrapperは、保守対象を増やすだけです。

第6章 既定は厳格にし、寛容さは明示させる

6.1 黙って補正すると「成功した誤答」になる

実務データでは、欠損列、重複、変換不能値が起きます。ここでライブラリが先頭行を返したり、欠損列を無視したりすると、更新は成功しても結果が誤っている可能性があります。

TLibは原則として、次のような状態をエラーにします。

  • 必須列がない

  • 1件であるべき検索結果が0件または複数件

  • 設定名がnull、空文字、非text

  • リソース指定recordが空、または複数フィールドを持つ

  • 解決したDataがtableではない

  • 指定型へ変換できない

6.2 0件と複数件は別の異常

table_lookup は検索条件に一致する「ちょうど1行」を返します。

Item =
    TLib[table_lookup](
        Source,
        [Category = "A", Code = 1001]
    )

0件なら見つからない異常、複数件なら一意性が壊れた異常です。「最初の1件」を返すと、二つの異常を隠してしまいます。

6.3 Error.Recordは調査材料を返す

error Error.Record(
    "DuplicateRecord",
    "検索条件に一致する行が複数見つかりました。",
    [Lookup = a_lookup, MatchCount = matched_count]
)

Reason、Message、Detailを分ければ、止まった事実だけでなく、原因を調査できます。エラーは不親切な停止ではなく、データ異常を利用者へ返すAPIです。

6.4 寛容動作は呼び出し側が選ぶ

欠損列を無視したい業務なら、利用側が MissingField.Ignore を明示します。

Prioritized =
    TLib[columns_prioritize](
        Source,
        {"重要列"},
        MissingField.Ignore
    )

第7章 型・引数順・名前を公開契約にする

7.1 主要入力を第1引数へ置く

TLibは、Power Query標準関数と同じように、変換対象を第1引数へ置きます。

columns_type_convert(
    a_table,
    a_column_names,
    a_target_type,
    optional a_culture
)

先頭から読めば、「どのtableを変換するのか」が分かります。必須引数を先、optional引数を後に置き、引数の有無や型で意味を切り替える万能APIを避けます。

7.2 名前は対象を先にする

table_lookup
path_join
path_parse
columns_prioritize
columns_type_convert
value_is_blank

「対象_操作」でそろえると、一覧表示や検索で関連関数がまとまります。関数名に入りきらない制約は、README、コメント、エラー契約へ置きます。

第8章 Mらしい値をAPIに使う

8.1 検索条件をrecordで表す

table_lookup は、検索条件をrecordで受け取ります。

[Category = "A", Code = 1001]

フィールド名が列名、フィールド値が検索値になり、複数フィールドはAND条件です。検索列が増えても、引数個数を増やす必要はありません。

ただし、このrecordの意味は検索条件だけです。万能なoptions recordにはしません。

8.2 複数結果を名前付きrecordで返す

list_partition は一致と不一致を次のrecordで返します。

[
    Matched = {...},
    Unmatched = {...}
]

位置ではなくフィールド名で結果の意味を読めます。

8.3 振る舞いをfunction値として渡す

States =
    TLib[list_scan](
        {1, 2, 3, 4},
        0,
        (a_state, a_item) => a_state + a_item
    )

Mでは関数も値なので、処理方法を引数として渡せます。ただし、固定値を閉じ込めたいだけなら、汎用カリー化APIを増やすより、利用側で直接クロージャーを書く方が単純です。

第9章 ライブラリ自身に説明能力を持たせる

9.1 READMEを公開値にする

TLibは、次の5列を持つREADME tableを公開しています。

FunctionName
Parameters
RequiredCount
ReturnType
Signature
Docs = TLib[README]

型情報は Value.Type、Type.FunctionParameters、Type.FunctionRequiredParameters、Type.FunctionReturn などから取得されます。シグネチャを手書きで二重管理する範囲を減らせます。

9.2 個別ヘルプを入口から引く

Help = TLib[function_help]("table_lookup")

利用者は1,505行の正本を検索しなくても、公開面から関数情報へ到達できます。

9.3 自己記述にも同期検査が必要

READMEを自動生成しても、公開関数を列挙するrecord、top-levelの公開フィールド、コメントのPublic members一覧には同期が必要です。自己記述機能だけで登録漏れが消えるわけではありません。

公開面を静的に照合するテストや、リリース用単一ファイルを生成する仕組みを組み合わせる余地があります。

第10章 作るべきでないライブラリ

10.1 巨大万能関数

read_anything(
    a_source as any,
    optional a_options as any
) as any =>
    ...

引数の型やoptionsの内容によって、取得、選択、ヘッダー昇格、型変換、欠損補完まで切り替える関数は、呼び出しから結果を予測できません。

10.2 失敗を隠す関数

列がなければ無視
0件ならnull
複数件なら先頭
変換できなければ元の値

寛容さを重ねるほど、処理成功と正しい結果を区別できなくなります。

10.3 業務固有関数を共有層へ混ぜる

sales_report_import のような案件固有の組み立ては、共有ライブラリより利用側クエリへ置く方が変更理由を追いやすくなります。共有層には、業務をまたいで意味が変わらない変換契約を置きます。

10.4 抽象化のための抽象化

汎用resolver、関数registry、カリー化エンジン、依存注入containerは、同じ具体的問題が繰り返された後に検討します。let ... in と直接クロージャーで読めるなら、それが最小の設計です。

まとめ 良いライブラリは「何をしないか」まで明確

Power Queryライブラリを設計するときは、次を確認します。

  • 公開面が一つの入口から確認できるか。

  • 便利な入口と組み合わせ可能な部品が区別されているか。

  • データ取得、対象選択、データ変換が分かれているか。

  • 下流へ必要最小限の値だけを渡しているか。

  • 標準M関数の知識を壊さない薄いwrapperか。

  • 0件、重複、欠損、変換不能時の契約が決まっているか。

  • 型、引数順、名前から呼び出しを予測できるか。

  • README、バージョン、導入手順、回帰テストまで運用に含めているか。

  • 反復していない問題を先回りして抽象化していないか。

再利用のために処理を隠すのではありません。再利用できる大きさまで責務を分け、入力、出力、非処理、異常条件を見える形にします。

Power Queryで長く使えるライブラリは、何でもしてくれるライブラリではありません。何を受け取り、何を返し、何をせず、どこで止まるかが予測できるライブラリです。

参考資料

  • Microsoft Learn「Power Query M formula language」

  • Microsoft Learn「M Language values」

  • Microsoft Learn「Best practices when working with Power Query」

  • TakiLib Power Query正本 C:\Dropbox\takilib\taki3lib4PQ.txt

出典メモ

  • 元原稿: C:\Users\hoehoe\Downloads\記事ネタmd\Power_Queryライブラリ設計_TakiLib分析_30分記事元ネタ.md

  • TakiLibのバージョン、更新日時、行数、公開関数数、SHA-256は、2026年8月21日にローカル正本を再確認しました。

  • TakiLib固有の関数名、構造、エラー方針は上記スナップショットを対象とした分析です。将来の版では変更される可能性があります。

  • Mのfunction値、record、型に関する一般事項はMicrosoft公式仕様を参照しました。

  • 公式資料: https://learn.microsoft.com/en-us/powerquery-m/

  • 公式資料: https://learn.microsoft.com/en-us/powerquery-m/m-spec-values

  • 公式資料: https://learn.microsoft.com/en-us/power-query/best-practices