msbuild-antipatterns
MSBuildのアンチパターンをカタログ化し、検出ルールと修正手順をまとめたスキルです。`.csproj`・`.vbproj`・`.fsproj`・`.props`・`.targets`・`.proj`ファイルのレビュー、監査、クリーンアップ時に有効で、各アンチパターンに症状・解説・BAD→GOODの具体的な変換例を提供します。Execタスクの誤用、クォートなし条件式、ハードコードされたパス、SDKデフォルト値の重複定義、パッケージバージョンの分散管理などを対象とし、npm・Maven・CMakeなど非MSBuildのビルドシステムやSDKスタイルへの移行(`msbuild-modernization`を使用)には対応していません。
description の原文を見る
Catalog of MSBuild anti-patterns with detection rules and fix recipes. Only activate in MSBuild/.NET build context. USE FOR: reviewing, auditing, or cleaning up .csproj, .vbproj, .fsproj, .props, .targets, or .proj files. Each anti-pattern has a symptom, explanation, and concrete BAD→GOOD transformation. Covers Exec-instead-of-built-in-task, unquoted conditions, hardcoded paths, restating SDK defaults, scattered package versions, and more. DO NOT USE FOR: non-MSBuild build systems (npm, Maven, CMake, etc.), project migration to SDK-style (use msbuild-modernization).
SKILL.md 本文
MSBuild アンチパターン・カタログ
一般的な MSBuild アンチパターンの番号付きカタログです。各エントリは以下の形式に従います:
- Smell: 何を探すか
- Why it's bad: ビルド、保守性、正確性への影響
- Fix: 具体的な変換
プロジェクトファイルをスキャンして改善を探す際に、このカタログを使用してください。
AP-01: 組み込みタスクで対応できる操作で <Exec> を使用
Smell: <Exec Command="mkdir ..." />、<Exec Command="copy ..." />、<Exec Command="del ..." />
Why it's bad: 組み込みタスクはクロスプラットフォーム対応、インクリメンタルビルドをサポート、構造化ログを発行、エラーを一貫して処理します。<Exec> は MSBuild に対して不透明です。
<!-- BAD -->
<Target Name="PrepareOutput">
<Exec Command="mkdir $(OutputPath)logs" />
<Exec Command="copy config.json $(OutputPath)" />
<Exec Command="del $(IntermediateOutputPath)*.tmp" />
</Target>
<!-- GOOD -->
<Target Name="PrepareOutput">
<MakeDir Directories="$(OutputPath)logs" />
<Copy SourceFiles="config.json" DestinationFolder="$(OutputPath)" />
<Delete Files="@(TempFiles)" />
</Target>
組み込みタスクの代替案:
| シェルコマンド | MSBuild タスク |
|---|---|
mkdir | <MakeDir> |
copy / cp | <Copy> |
del / rm | <Delete> |
move / mv | <Move> |
echo text > file | <WriteLinesToFile> |
touch | <Touch> |
xcopy /s | <Copy> with item globs |
AP-02: クォート無しの条件式
Smell: Condition="$(Foo) == Bar" — 比較の片側または両側がクォートされていない。
Why it's bad: プロパティが空か、スペースや特殊文字を含む場合、条件が正しく評価されないか、パースエラーが発生します。MSBuild は信頼性の高い比較のために、単一引用符で文字列をクォートする必要があります。
<!-- BAD -->
<PropertyGroup Condition="$(Configuration) == Release">
<Optimize>true</Optimize>
</PropertyGroup>
<!-- GOOD -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
</PropertyGroup>
ルール: == と != 比較の 両側 を常に単一引用符でクォートしてください。
AP-03: ハードコードされた絶対パス
Smell: プロジェクトファイル内の C:\tools\、D:\packages\、/usr/local/bin/ のようなパス。
Why it's bad: 他のマシン、CI 環境、他のオペレーティングシステムで破損します。再配置不可能です。
<!-- BAD -->
<PropertyGroup>
<ToolPath>C:\tools\mytool\mytool.exe</ToolPath>
</PropertyGroup>
<Import Project="C:\repos\shared\common.props" />
<!-- GOOD -->
<PropertyGroup>
<ToolPath>$(MSBuildThisFileDirectory)tools\mytool\mytool.exe</ToolPath>
</PropertyGroup>
<Import Project="$(RepoRoot)eng\common.props" />
推奨パスプロパティ:
| プロパティ | 意味 |
|---|---|
$(MSBuildThisFileDirectory) | 現在の .props/.targets ファイルのディレクトリ |
$(MSBuildProjectDirectory) | .csproj のディレクトリ |
$([MSBuild]::GetDirectoryNameOfFileAbove(...)) | マーカーファイルを探して上へ歩く |
$([MSBuild]::NormalizePath(...)) | パスセグメントを結合して正規化 |
AP-04: SDK デフォルトの再述
Smell: .NET SDK が既にデフォルトで提供している値に設定されたプロパティ。
Why it's bad: ノイズが増える、意図的な上書きが隠される、何が実際にカスタマイズされているかわかりにくくなります。新しい SDK でデフォルトが変更されると、冗長なプロパティは黙って古い動作にロックされる可能性があります。
<!-- BAD: これらはすべて既にデフォルト -->
<PropertyGroup>
<OutputType>Library</OutputType>
<EnableDefaultItems>true</EnableDefaultItems>
<EnableDefaultCompileItems>true</EnableDefaultCompileItems>
<RootNamespace>MyLib</RootNamespace> <!-- matches project name -->
<AssemblyName>MyLib</AssemblyName> <!-- matches project name -->
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<!-- GOOD: デフォルト以外の値のみ -->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
AP-05: SDK スタイルプロジェクトでの手動ファイルリスティング
Smell: SDK スタイルプロジェクトで <Compile Include="File1.cs" />、<Compile Include="File2.cs" /> など。
Why it's bad: SDK スタイルプロジェクトは自動的に **/*.cs (および他のファイル型) をグロブします。明示的なリスティングは冗長、マージコンフリクトを作成、新しいファイルはリストに追加されないと誤って漏れる可能性があります。
<!-- BAD -->
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Services\MyService.cs" />
<Compile Include="Models\User.cs" />
</ItemGroup>
<!-- GOOD: 完全に削除する — SDK はデフォルトですべての .cs ファイルを含みます。
除外したいときのみ Remove/Exclude を使用: -->
<ItemGroup>
<Compile Remove="LegacyCode\**" />
</ItemGroup>
例外: 非 SDK スタイル (レガシー) プロジェクトはファイルの明示的インクルードが必要です。移行する場合は、msbuild-modernization スキルを参照してください。
例外 (F# / .fsproj): F# のコンパイルは順序に依存します — コンパイラは <Compile Include> アイテムを順次処理し、ファイルは上にリストされているファイルで宣言された型/モジュールのみを参照できます。.fsproj ファイルはすべてのソースファイルを明示的に、依存関係の順に (上部のユーティリティ/葉モジュール、下部の Program.fs のようなエントリポイント) リストする必要があります。.fsi シグネチャファイルを使用する場合、コンパニオン .fs 実装ファイルの 直前に 現れる必要があります。
AP-06: NuGet パッケージに HintPath 付きで <Reference> を使用
Smell: <Reference Include="..." HintPath="..\packages\SomePackage\lib\..." />
Why it's bad: これはレガシー packages.config パターンです。推移的依存関係、バージョン競合解決、自動復元をサポートしません。packages/ フォルダはコミットするか別途復元する必要があります。
<!-- BAD -->
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\netstandard2.0\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
<!-- GOOD -->
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
注記: HintPath 無しの <Reference> は WindowsBase、PresentationCore などの .NET Framework GAC アセンブリには有効です。
AP-07: アナライザー/ツールパッケージに PrivateAssets="all" がない
Smell: <PackageReference Include="StyleCop.Analyzers" Version="..." /> で PrivateAssets="all" がない。
Why it's bad: PrivateAssets="all" がないと、アナライザーとビルドツールパッケージはあなたのライブラリのコンシューマーに推移的依存関係として流れます。コンシューマーは要求していないアナライザーやビルド時ツールを取得します。
BAD/GOOD 例と必要なパッケージの完全なリストについては、 を参照してください。references/private-assets.md
AP-08: 複数の .csproj ファイル間でのコピーペーストプロパティ
Smell: 同じ <PropertyGroup> ブロックが 3 個以上のプロジェクトファイルに表示される。
Why it's bad: メンテナンス負荷 — 変更はすべてのファイルで行う必要があります。不整合が時間とともに忍び込みます。
<!-- BAD: すべての .csproj に繰り返される -->
<!-- ProjectA.csproj, ProjectB.csproj, ProjectC.csproj すべてが: -->
<PropertyGroup>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<!-- GOOD: リポジトリ/src ルートの Directory.Build.props で一度定義 -->
<!-- Directory.Build.props -->
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Directory.Build.props / Directory.Build.targets 構造化の完全なガイダンスについては、directory-build-organization スキルを参照してください。
AP-09: 一元化されたパッケージ管理なしの分散したパッケージバージョン
Smell: <PackageReference Include="X" Version="1.2.3" /> で同じパッケージの異なるバージョンがプロジェクト間で異なる。
Why it's bad: バージョンドリフト — 異なるプロジェクトが同じパッケージの異なるバージョンを使用し、ランタイム不一致、予期しない動作、またはダイヤモンド依存関係競合につながります。
<!-- BAD: バージョンが各プロジェクトで指定され、ドリフトの可能性 -->
<!-- ProjectA.csproj -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<!-- ProjectB.csproj -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
修正: 一元化パッケージ管理を使用します。詳細は https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management を参照してください。
AP-10: モノリシックターゲット (1 つのターゲットに詰め込みすぎ)
Smell: 50 行以上で複数の無関係なことをしている単一の <Target>。
Why it's bad: インクリメンタルビルド経由で個々のステップをスキップできない、デバッグが困難、拡張が困難、ターゲット名が無意味になります。
<!-- BAD -->
<Target Name="PrepareRelease" BeforeTargets="Build">
<WriteLinesToFile File="version.txt" Lines="$(Version)" Overwrite="true" />
<Copy SourceFiles="LICENSE" DestinationFolder="$(OutputPath)" />
<Exec Command="signtool sign /f cert.pfx $(OutputPath)*.dll" />
<MakeDir Directories="$(OutputPath)docs" />
<Copy SourceFiles="@(DocFiles)" DestinationFolder="$(OutputPath)docs" />
<!-- ... さらに 30 行 ... -->
</Target>
<!-- GOOD: 単一責任ターゲット -->
<Target Name="WriteVersionFile" BeforeTargets="CoreCompile"
Inputs="$(MSBuildProjectFile)" Outputs="$(IntermediateOutputPath)version.txt">
<WriteLinesToFile File="$(IntermediateOutputPath)version.txt" Lines="$(Version)" Overwrite="true" />
</Target>
<Target Name="CopyLicense" AfterTargets="Build">
<Copy SourceFiles="LICENSE" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true" />
</Target>
<Target Name="SignAssemblies" AfterTargets="Build" DependsOnTargets="CopyLicense"
Condition="'$(SignAssemblies)' == 'true'">
<Exec Command="signtool sign /f cert.pfx %(AssemblyFiles.Identity)" />
</Target>
AP-11: Inputs と Outputs がないカスタムターゲット
Smell: <Target Name="MyTarget" BeforeTargets="Build"> で Inputs / Outputs 属性がない。
Why it's bad: ターゲットは何も変わらなくてもビルドのたびに実行され、インクリメンタルビルドを損ないます。これはノーオップビルドを遅くします。
BAD/GOOD 例と FileWrites 登録を含む完全なパターンについては、 を参照してください。references/incremental-build-inputs-outputs.md
Inputs/Outputs、FileWrites、および最新性チェックの詳しいガイダンスについては、incremental-build スキルを参照してください。
AP-12: デフォルトを .targets ではなく .props で設定
Smell: .targets ファイル内でデフォルト値を持つ <PropertyGroup>。
Why it's bad: .targets ファイルは遅くインポートされます (プロジェクトファイルの後)。デフォルトを設定するときまでに、他の .targets ファイルが既に空/未定義の値を使用している可能性があります。.props ファイルは早くインポートされ、デフォルトの正しい場所です。
<!-- BAD: custom.targets -->
<PropertyGroup>
<MyToolVersion>2.0</MyToolVersion>
</PropertyGroup>
<Target Name="RunMyTool">
<Exec Command="mytool --version $(MyToolVersion)" />
</Target>
<!-- GOOD: .props (デフォルト) + .targets (ロジック) に分割 -->
<!-- custom.props (早くインポート) -->
<PropertyGroup>
<MyToolVersion Condition="'$(MyToolVersion)' == ''">2.0</MyToolVersion>
</PropertyGroup>
<!-- custom.targets (遅くインポート) -->
<Target Name="RunMyTool">
<Exec Command="mytool --version $(MyToolVersion)" />
</Target>
ルール: .props = デフォルトと設定 (早期評価)。.targets = ビルドロジックとターゲット (遅期評価)。
AP-13: Exists() ガード無しのインポート
Smell: <Import Project="some-file.props" /> に Condition="Exists('...')" チェックがない。
Why it's bad: ファイルが存在しない場合 (未作成、パス間違い、削除) ビルドが失敗し、混乱させるエラーが発生します。オプションのインポートは常にガードすべきです。
<!-- BAD -->
<Import Project="$(RepoRoot)eng\custom.props" />
<!-- GOOD: オプションのインポートをガード -->
<Import Project="$(RepoRoot)eng\custom.props" Condition="Exists('$(RepoRoot)eng\custom.props')" />
<!-- また GOOD: Sdk 属性インポートはガード不要 (設計上必須) -->
<Project Sdk="Microsoft.NET.Sdk">
例外: ビルドが正しく動作するために 必須 なインポートは高速に失敗すべき — それらはガードしません。オプションまたは環境固有のインポート (例: ローカル開発者上書き、CI 固有設定) はガードします。
AP-14: パスでのバックスラッシュ使用 (クロスプラットフォーム問題)
Smell: クロスプラットフォーム対応を意図した .props/.targets ファイル内で、バックスラッシュセパレータがある <Import Project="$(RepoRoot)\eng\common.props" />。
Why it's bad: バックスラッシュは Windows で動作しますが、Linux/macOS で失敗します。MSBuild はすべてのプラットフォームでフォワードスラッシュを正規化します。
<!-- BAD: Linux/macOS で破損 -->
<Import Project="$(RepoRoot)\eng\common.props" />
<Content Include="assets\images\**" />
<!-- GOOD: フォワードスラッシュはすべての場所で動作 -->
<Import Project="$(RepoRoot)/eng/common.props" />
<Content Include="assets/images/**" />
注記: $(MSBuildThisFileDirectory) は既にプラットフォーム適切なセパレータで終わるため、$(MSBuildThisFileDirectory)tools/mytool は両方のプラットフォームで動作します。
AP-15: 複数スコープでの無条件プロパティ上書き
Smell: プロパティが Directory.Build.props と .csproj の両方で無条件に設定 — 最後の書き込みが黙って優先します。
Why it's bad: どの値が実際に使用されているかを追跡するのが困難です。ビルドが脆く、プロジェクトファイルを読む誰もが混乱します。
<!-- BAD: Directory.Build.props が設定、csproj が黙って上書き -->
<!-- Directory.Build.props -->
<PropertyGroup>
<OutputPath>bin\custom\</OutputPath>
</PropertyGroup>
<!-- MyProject.csproj -->
<PropertyGroup>
<OutputPath>bin\other\</OutputPath>
</PropertyGroup>
<!-- GOOD: 上書きが意図的であることが明確になるよう条件を使用 -->
<!-- Directory.Build.props -->
<PropertyGroup>
<OutputPath Condition="'$(OutputPath)' == ''">bin\custom\</OutputPath>
</PropertyGroup>
<!-- MyProject.csproj は意図的にデフォルトを上書きするか、そのままにできます -->
追加のアンチパターン (AP-16 から AP-21) とクイックリファレンスチェックリストについては、additional-antipatterns.md を参照してください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- dotnet
- リポジトリ
- dotnet/skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/dotnet/skills / ライセンス: MIT
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。