Skip to content

Latest commit

 

History

History
461 lines (345 loc) · 20.5 KB

File metadata and controls

461 lines (345 loc) · 20.5 KB

CODING.md — コーディング規約

対象: root/programs 配下(C# / bat / ps1 / Markdown) 配置: root

既存コードに合わせること。

一次情報は本書ではない。

内容 一次情報
コメント量、クロスコンパイル方針、git-flow、PR の粒度 ../Contributing.ja.md
エージェント固有の制約(git 操作、GitHub 操作、秘密の扱い) ../AGENTS.md
各領域の構成と落とし穴 各フォルダの ANALYSIS.md

本書は、そこに書かれていないファイル形式ごとの約束を扱う。


1. ファイル ヘッダ

.cs の冒頭は、Apache License のブロックとクラス ヘッダを持つ。新規追加時も付ける。

//**********************************************************************************
//* Copyright (C) 2026 Hitachi Solutions,Ltd.
//**********************************************************************************

#region Apache License
// ...
#endregion

//**********************************************************************************
//* クラス名        :CmnIdToken
//* クラス日本語名  :CmnIdToken
//*
//* 作成日時        :-
//* 作成者          :-
//* 更新履歴        :-
//*
//*  日時        更新者            内容
//*  ----------  ----------------  -------------------------------------------------
//*  2019/02/12  西野 大介         新規
//*  2026/09/07  玄人 幸道         nonce無しでもid_tokenを発行するよう修正(#183)
//**********************************************************************************

変更履歴はヘッダの履歴だけ継続するContributing.ja.md)。 コード中に「修正の開始・終了」を書かない。

更新者名

変更した人 「更新者」に書く名前
メンテナ 西野 大介
Claude Code(エージェント) 玄人 幸道

既存行の名前を流用しない。 誰が入れた変更かを後から追えるようにするため。 エージェントの作業と人の作業を混ぜて記録すると、レビューの重み付けができなくなる。

「玄人 幸道」は「西野 大介」と表示幅が同じなので、 既存行のパディング(名前の後ろに半角空白 9 個)をそのまま使えば桁が揃う。

Git のコミット著者は人であり、これとは別。エージェントは git 操作をしないAGENTS.md)。

2. クロスコンパイル

net48 と net10.0 は別系列なので、条件付きコンパイルで分ける。

#if NETFX
    // net48
#else
    // net10.0
#endif
シンボル 定義するプロジェクト 使用箇所(CommonLibrary
NETFX NetFxLibrary.csproj #if NETFX が 71 箇所
NETCORE NetCoreLibrary.csproj #if NETCORE が 5 箇所

#if NETFX を主に使う。 #if NETCORE は、Core にしか無いものを足すときだけ。

ソースの出し入れは csproj が違う

形式 ファイルの指定
NetFxLibrary.csproj 旧形式 <Compile Include> を 1 本ずつ明記(84 個)
NetCoreLibrary.csproj SDK 形式 既定で全部入り、<Compile Remove> で除外

ファイルを追加したら、両方に反映が要る。 NetFxLibrary.csprojInclude を足し忘れると、 net10.0 では通るのに net48 でだけ「型が無い」になる。

.resx に項目を足したら、.Designer.cs も直す

.Designer.cs はリポジトリに入っている生成コードで、dotnet build では再生成されない (Visual Studio が .resx の保存時に作る)。

[Display(Name = "RequirePkce", ResourceType = typeof(Resources.CommonViewModels))]

DisplayAttribute は、実行時に同名の public static プロパティを反射で探す。 .Designer.cs に無ければ、ビューを描画した時点で例外になる。

ビルドでは分からない。 Name はただの文字列なので、コンパイル時に検証されない。 E2E でも分からないことが多い(管理画面を操作するテストが無い)。

.resx(既定)と .ja.resx(サテライト)の両方に値を入れ、 .Designer.cs には既定の方に合わせてプロパティを 1 つ足す。

構成ごとにシンボルを書き落とさない

DefineConstants は構成(Debug / Release)ごとに別々に書く。 片方に書き忘れられる。

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  <DefineConstants>TRACE;DEBUG;NETFX</DefineConstants>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
  <DefineConstants>TRACE;NETFX</DefineConstants>

実際に踏んだ。 NetFxLibrary.csproj の Release に NETFX が無く、 #else(Core 側)が採られてコンパイルが通らなかった。

Co/Config.cs(49,28): error CS0234: 'Configuration' が名前空間 'Microsoft.Extensions' に存在しません
Data/CmnUserStore.cs(59,17): error CS0234: 'AspNetCore' が名前空間 'Microsoft' に存在しません

ビルド バッチが BUILD_CONFIG=Debug 固定だったため、長く露見しなかった。 1_BuildAll.ps1 -Configuration Release を入れて初めて出た。修正済み。

CommandLineTools の net 版は、Debug / Release の両方に NET を持っている。そちらが手本。

3. 改行コードと、置換スクリプト

改行コードは混在している

root/programs.cs は、ファイルごとに CRLF と LF が混ざっている。

改行
CommonLibrary/TokenProviders/CmnAccessToken.cs CRLF
CommonLibrary/TokenProviders/CmnIdToken.cs CRLF
CommonLibrary/TokenProviders/CmnEndpoints.cs LF
MultiPurposeAuthSiteCore/.../AccountController.cs LF

変えてはならない。 改行を変えると git がファイル全体を差分として扱い、レビューできなくなる。 Contributing.ja.md も「IDE や Editor によりインデントが変更されるような不要な修正もコミットしない」と定めている。

スクリプトで複数行を置換するときは、対象ファイルの改行を検出してから組み立てる。

nl = "\r\n" if "\r\n" in txt else "\n"

置換文字列に nl を埋め込んだうえで、さらに .replace("\n", nl) を掛けないこと。 CRLF のファイルで "\r\n".replace("\n", "\r\n")"\r\r\n" になり、ファイル全体が差分になる。

確認は git diff --numstat の行数で行う。 git show HEAD:<path> は blob を LF 正規化して出すので、改行の比較には使えない。

sed -i は行中の置換なら改行を保つので安全。複数行にまたがるときだけ注意する。

置換スクリプトの文字列はエスケープを通る

Python の通常文字列に \2 と書くと、文字コード 2 の制御文字になる。 .\2_RunAllTests.ps1 のような Windows のパスやコマンドを 文書へ埋め込むときに踏む。ファイルは壊れるが、見た目では気付きにくい。

> ._RunAllTests.ps1      ← \2 が消えている(実際には \x02 が入っている)

同様に \0(NUL)、\p\M(警告のうえ、将来の Python では壊れる)も同じ。

置換文字列は生文字列(raw string)で書く。 書き込んだ後に、制御文字が入っていないかを確かめる。

bad = [hex(ord(c)) for c in s if ord(c) < 32 and c not in "\r\n\t"]

ヒアドキュメントは \\ に縮める。 シェル経由でスクリプトを渡すと、書いたはずの二重エスケープが一重で届く。 確実にやるなら、スクリプトをファイルに書いてから実行する。

4. bat ファイル

重要な bat は、非 ASCII を一切書かない。 コメントも英語にする。

cmd.exe はバッチをバイト オフセットで読み進めるため、非 ASCII があると コンソールのコード ページ次第で文字境界がずれ、 @rem コメントの途中から先がコマンドとして実行されることがある。

  • BOM は緩和にはなるが、保証ではない(対話コンソールで再現することがある)
  • chcp 65001 を中に書くと、むしろ悪化する。 途中でコード ページが変わる分、条件が増える
  • 非対話(cmd /c)では再現しない。 手元で確認しても気付けない

z_Common.batすべてのビルド バッチが呼ぶので、純 ASCII にしてある。 先頭に NOTE: keep this file pure ASCII. と理由が書いてある。

非 ASCII の役割 対処
コメント・echo ASCII 化する(重要な bat では必須)
外部プログラムへ渡す引数 消せない。コンソールのコード ページに合わせて符号化する

純 ASCII なら BOM は不要(差分ノイズになるだけ)。 日本語を書き足すときは、BOM の有無を確認すること。

現状、root/programs の bat は次のとおり。

ファイル 非 ASCII
z_Common.bat / 0_ExecAllBat.bat / 10_*.bat / 2_DeleteFile.bat 無し
1_DeleteDir.bat / z_Common2.bat / 3_BuildLibsAtOtherRepos*.bat 有り(コメントのみ)

5. ps1 ファイル

Windows PowerShell 5.1 と PowerShell 7 の両方で動くこと。

開発時は pwsh(7)で確認しがちだが、利用者は powershell.exe(5.1)で実行する。

事象 原因 対処
構文エラー・文字化け(繧オ繧、繝 5.1 は BOM 無しの .ps1 を **ANSI(Shift_JIS)**として読む UTF-8 BOM + CRLF で保存する
Get-Content の結果が違う 既定エンコードが 5.1 は ANSI、7 は UTF-8 -Encoding UTF8 を明示する
自己署名証明書の HTTPS が叩けない API ごとに、動く版が違う(下の表) 版で分岐する
-File で単体起動したときだけ Join-Path が落ちる [CmdletBinding()] があると、5.1 は param() の既定値を評価する時点で $PSScriptRoot が空 パスの既定値は param() に書かず、本体で決める
表の見出し・罫線・データがずれる 5.1 の Format-Table桁数ではなく文字数で幅を決める(全角は 1 文字で 2 桁) SummaryTable.ps1Write-SummaryTable を使う

1 行目は実際に踏んだ。 test.ps1 だけ BOM 無しで作ってしまい、 5.1 から 0_RunAll.ps1 を実行すると日本語コメントが化けて クォートの対応が壊れ、構文エラーになった。 ここに書いてある落とし穴を、この文書を書いた本人が踏んでいる。 .ps1 を足したら、必ず 5.1 でも構文検査すること。

$PSScriptRootparam() の既定値で使わない

実測(Windows PowerShell 5.1 / PowerShell 7)。

スクリプトの形 5.1 -File 7 -File
param(...) だけ 入る 入る
[CmdletBinding()]param(...) 入る
Join-Path : Cannot bind argument to parameter 'Path' because it is an empty string.

0_RunAll.ps1 から & で呼ぶ分には呼び出し元の値が見えるため表面化しない。 単体で -File 起動したときだけ落ちるので、通しの確認では見つからない。

# 悪い
[CmdletBinding()]
param([string]$OutputDir = (Join-Path $PSScriptRoot "logs"))

# 良い
[CmdletBinding()]
param([string]$OutputDir)

if (-not $OutputDir)
{
    $OutputDir = Join-Path $PSScriptRoot "logs"
}

自己署名証明書の HTTPS(5.1 / 7 で API を分ける)

同じ書き方で両方は通らなかった。 開発用証明書の Kestrel に対する実測。

方法 5.1 7
Invoke-WebRequest NG OK(-SkipCertificateCheck
HttpWebRequestServicePointManager のコールバック OK NG
HttpWebRequest + 個別のコールバック NG
HttpClient + コールバック NG OK
  • 5.1 の NG : 接続が切断されました: 送信時に、予期しないエラーが発生しました。
  • 7 の NG : The SSL connection could not be established

生の SslStream は 5.1 でも TLS 1.2 / 1.3 の両方で成功する。TLS そのものの問題ではない。 -UseBasicParsing / -Proxy $null / -DisableKeepAlive のいずれでも変わらなかった。

原因を追うより、それぞれで通ることを確認した方法を使う。

if ($PSVersionTable.PSVersion.Major -ge 6)
{
    $res  = Invoke-WebRequest -Uri $url -TimeoutSec 5 -SkipCertificateCheck
    $code = [int]$res.StatusCode
}
else
{
    # **スクリプト ブロックではなく、コンパイルしたデリゲートを使う**(下記)
    [MpasTestTls]::TrustAll()
    [System.Net.ServicePointManager]::SecurityProtocol =
        [System.Net.SecurityProtocolType]::Tls12

    $req = [System.Net.HttpWebRequest]::Create($url)
    $req.Timeout = 5000
    $res  = $req.GetResponse()
    $code = [int]$res.StatusCode
    $res.Close()
}

5.1 のコールバックは、スクリプト ブロックにしない(#226 で実測)。

サーバがクライアント証明書を要求すると(mTLS)、サーバ証明書の検証が ランスペースの無いスレッドから呼ばれる。 スクリプト ブロックだと

このスレッドには、スクリプトを実行するために使用できる実行空間が存在しません

になり、ハンドシェイクごと落ちる(呼び出し側には「接続が切断されました」としか見えない)。 要求されないうちは同期的に呼ばれるので、mTLS を使うまで表面化しない。

Add-Type -TypeDefinition @"
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

public static class MpasTestTls
{
    public static void TrustAll()
    {
        ServicePointManager.ServerCertificateValidationCallback =
            delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
            { return true; };
    }
}
"@
[MpasTestTls]::TrustAll()
# 集計表は Format-Table ではなく、桁数を自前で数える整形を使う
. (Join-Path $PSScriptRoot "SummaryTable.ps1")
Write-SummaryTable $results

その他。

  • 要素 1 個の配列は、返した時点でスカラーに展開される。 そのまま [0] を取ると 文字列の 1 文字目になる。関数の戻り値を添字で使うなら @() で受ける
  • $PSScriptRoot で組み立てる。 ダブル クリック起動でカレントに依存しないようにする
  • Read-Host で締めるのは、ダブル クリックする最上位(0_RunAll.ps1)だけ。 途中のスクリプトに入れると、通しで回せなくなる
  • 例外を握り潰さない。 再試行する catch でも最後の理由は残す。 時間切れになったとき、理由が無いと原因が分からない
  • 子プロセスの出力はファイルへ残す-RedirectStandardOutput / -RedirectStandardError)。 「応答しません」だけでは、落ちたのか起動中なのかも分からない
  • 待ち時間は回数ではなく実時間で測る。 接続拒否は即座に返るが、 起動中は Timeout まで待つため、回数だと上限が数倍変わる
  • 変更したら 5.1 でも実行して確かめること
# 構文検査
powershell.exe -NoProfile -Command "$e=$null; [void][System.Management.Automation.Language.Parser]::ParseFile('root\1_BuildAll.ps1',[ref]$null,[ref]$e); $e"

# 通し(5.1 / 7 の両方で)
powershell.exe -NoProfile -File "root\0_RunAll.ps1" -SkipClean
pwsh           -NoProfile -File "root\0_RunAll.ps1" -SkipClean

# **単体でも起動してみること。** 通しでは表面化しない不具合がある
powershell.exe -NoProfile -File "root\1_BuildAll.ps1" -List
powershell.exe -NoProfile -File "root\2_RunAllTests.ps1" -Launch

子プロセスの日本語が化ける(5.1)

dotnet の出力は UTF-8。5.1 は既定(ANSI = 932)で読むため化ける。

  蠕ゥ蜈・ッセ雎。縺ョ繝励Ο繧ク繧ァ繧ッ繝医r豎コ螳壹@縺ヲ縺・∪縺・..   ← 「復元対象のプロジェクトを決定しています...」

7 は既定が UTF-8 なので出ない。5.1 のときだけ、実行の間の読み取りを UTF-8 にし、終わったら戻す。 コンソールの設定を変えるので、finally で必ず戻すこと(chcp を呼ぶ必要は無い)。

$prev = $null
if ($PSVersionTable.PSVersion.Major -lt 6) {
    try {
        $prev = [Console]::OutputEncoding
        [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false
    }
    catch { $prev = $null }   # コンソールが無いとき
}
try     { & dotnet @args }
finally { if ($null -ne $prev) { try { [Console]::OutputEncoding = $prev } catch { } } }

ネイティブ コマンドの標準エラーで止まる(5.1)

Windows PowerShell 5.1 は、出力をリダイレクトしているとき、ネイティブ コマンドの 標準エラー出力を 1 行ずつ ErrorRecord(NativeCommandError)に包む。 $ErrorActionPreference = 'Stop' のスクリプトでは、その 1 行目でスクリプトが止まる。

xUnit は、失敗したテストの [FAIL] 行を標準エラーに書く。このため test.ps1 は、 テストが 1 件でも失敗すると、集計も報告書も作らずに止まっていた。 全件成功している間は標準エラーに何も出ないので、表面化しなかった。 (> log 2>&1 で出力を取っていて、はじめて起きた。)

$eap = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
    & dotnet @testArgs 2>&1 | ForEach-Object { "$_" }
    $exitCode = $LASTEXITCODE
}
finally {
    $ErrorActionPreference = $eap
}

合否は $LASTEXITCODE と TRX で判定する。"$_" で文字列にすると、 At line:... の付記も付かず、ログがテストの出力だけになる。

書式

SummaryTable.ps1 は OpenTouryo リポジトリからの移植。あちらと足並みを揃える。 コメント ベースのヘルプ(.SYNOPSIS / .NOTES の更新履歴)は .cs のヘッダと同じ流儀で書く。

6. ANALYSIS.mdANALYSIS-IdP.md

点在する分析のスナップショットではなく、対応状況の一覧を兼ねる。

指摘した項目を修正したら、同じコミット(または直後)で次を行う。

  • 見出しの末尾に — **✅ 修正済み(#182)** を付ける。★最優先 などの優先度表記は外す
  • 本文の記述は消さない。 「何が問題だったか」を残したまま印を付ける
  • 修正前のコード引用には // 修正前: <パス> と明記する。行番号は修正で動くので書かない
  • 本文中の「現在こうなっている」という断定が偽になったら、そこも直す
  • ロードマップの表と、0 節の「対応状況」の件数も更新する

誤検出だったものは ⚠️ 誤検出(#Issue) にして、理由を残す。 消さない。

未修正のセキュリティ上の弱点は、ここに書く前に確認を取る。 この文書は公開なので、コミットした時点で弱点が公開されるAGENTS.md)。 修正済みの項目は、これまでどおり書く。

ずれた文書は、次に読む人が「まだ直っていない」と誤認する材料になる。

7. 秘密を書かない

app.config / appsettings.json の内容を、 コード・コメント・コミット メッセージ・Issue 本文・報告に転記しない。

設定の変更は雛形(_app.config / _appsettings.json)側に書く。 詳細は CONFIGURATION.md 6 節。

テストの出力にトークンを出さない。キー名とエラーだけを出す (TESTING.md 9 節)。