常見踩坑總覽
以下踩坑模式從多份跨專案 Code Review 報告中提取,每個模式至少在兩個專案出現過。
Async / 同步阻塞
在非同步上下文中使用 .GetAwaiter().GetResult() 或 .Wait() 進行同步等待,導致 deadlock 風險和執行緒池耗盡。
錯誤做法
// 在 async 上下文中同步等待
FlushInternalAsync(CancellationToken.None).GetAwaiter().GetResult();
_cleanup.Wait(TimeSpan.FromSeconds(3));
正確做法
await FlushInternalAsync(cancellationToken);
// 若必須 fire-and-forget:
_ = Task.Run(() => FlushInternalAsync(ct), ct);
Dispose / 資源洩漏
IDisposable 資源(如 Mat、IModbusClient、Timer)在某些程式碼路徑下未被正確 dispose,導致 memory leak 或 native resource 洩漏。
錯誤做法
var mat = new Mat(height, width, MatType.CV_8UC3, rawData);
_lastFrame = mat; // 舊的 _lastFrame 未 dispose
正確做法
var oldFrame = Interlocked.Exchange(ref _lastFrame, mat);
oldFrame?.Dispose();
Thread Safety / Race Condition
使用普通 bool flag 或非原子操作(如 _id++)進行並發控制,在多執行緒環境下產生 race condition。
錯誤做法
private bool _disposed; // 非 volatile,可能讀到過期值
_transactionId++; // 非 atomic 操作
正確做法
private volatile bool _disposed;
Interlocked.Increment(ref _transactionId);
CancellationToken 缺失
公開的 async 方法未接受 CancellationToken 參數,或未將其正確傳遞給下游呼叫,導致操作無法被取消。
錯誤做法
Task<bool> LoginAsync(string username, string password);
正確做法
Task<bool> LoginAsync(string username, string password,
CancellationToken cancellationToken = default);
Fire-and-Forget 無錯誤處理
使用 _ = SomeMethodAsync() 啟動非同步工作但無錯誤處理策略,例外被靜默吞掉,導致初始化失敗或資料遺失無法偵測。
錯誤做法
_ = InitializeAsync(); // 例外靜默消失
正確做法
_ = InitializeAsync().ContinueWith(t =>
_logger.LogError(t.Exception, "Init failed"),
TaskContinuationOptions.OnlyOnFaulted);
DI Lifetime 設計錯誤
在 WPF 等無 per-request scope 的環境中,Singleton 服務注入 Scoped 依賴(如 DbContext),導致 DbContext 永不釋放、Change Tracker 累積、多執行緒並發存取非 thread-safe 物件。
錯誤做法
services.AddDbContext<PlcDbContext>(); // Scoped
services.AddSingleton<ITimeSeriesService, TimeSeriesService>();
// TimeSeriesService 注入 PlcDbContext → DbContext 被 Singleton 捕獲
正確做法
services.AddDbContextFactory<PlcDbContext>(opt =>
opt.UseNpgsql(connectionString));
// Service 內每次建立新的 DbContext:
using var ctx = _factory.CreateDbContext();
命名規範違規 — Boolean 屬性缺少前綴
Boolean 屬性未使用 Is/Has/Can/Should 前綴,違反 GST 命名規範,降低程式碼可讀性。
錯誤做法
public bool RequirePasswordExpiry { get; set; }
public bool AllowCrossUserSigning { get; set; }
正確做法
public bool IsPasswordExpiryRequired { get; set; }
public bool IsCrossUserSigningAllowed { get; set; }
空 Catch Block / 靜默吞掉例外
使用空的 catch 區塊或僅 catch 而不記錄任何資訊,導致錯誤被靜默吞掉,debug 極為困難。
錯誤做法
try { DecodeFrame(data); }
catch { } // 完全靜默
正確做法
try { DecodeFrame(data); }
catch (Exception ex)
{ _logger.LogDebug(ex, "Frame decode failed"); }
其他待補充項目
以下項目尚待充實,歡迎透過 Ingest 流程 補充:
- 通訊協定:Modbus 設備離線偵測與重連策略、SECS/GEM 握手超時
- 資料庫:TimescaleDB hypertable 分區策略、高頻寫入批次最佳化
- 部署:.NET 單檔發布 vs 框架依賴部署、防毒軟體誤判
- UI:WPF DataGrid 大量資料綁定效能、ComponentOne 授權驗證時機
- RPA:圖像辨識解析度穩定性、SignalR 連線管理