Aplicação C# ‐ Sonômetro (Sugestao) - tecnologiadB/MonitoramentoRuidoWiki GitHub Wiki
Revisão Arquitetural e Clean Code — Service de Exportação (C#/.NET Framework)
Projeto analisado:
SoftwareExportacao/consoleEXPORTAR
Arquivos-chave:Program.cs,consoleEXPORTAR.csproj,Properties/AssemblyInfo*.cs
Contexto observado: Console app .NET Framework que orquestra instrumentação (SDKBK.BasicEnv.Application), lê configurações via INI, executa calibração/medição e realiza exportação de projetos para XML (api.ExportProjectToXML), além de chamadas HTTP com autenticação Basic.
1) Diagnóstico
- Monolito procedural no
Program.cs: regras de domínio, I/O de arquivos, orquestração do SDK e HTTP estão acopladas em um único fluxo longo comThread.Sleepe muitosif/elses. - Baixa testabilidade: uso predominante de
static, singletons implícitos do SDK, acesso direto aDateTime.Now,File.*,Console.*eThread.Sleep. - Resiliência frágil: sem timeouts ou retries consistentes; polling com
Sleepem calibração/medição; falta CancellationToken e backoff. - Configuração dispersa e frágil: INI + literais de chaves (ex.:
"CICStatus","StartCICCalibration"); ausência de opções tipadas e validação. - Segurança: autenticação HTTP Basic montada manualmente; risco de vazamento em logs; segredos provavelmente no INI.
- Legibilidade: muitos magic strings (ex.: tipos
"CPB","LoggingSLM","FFT","RT"), duplicação de lógica para exportação e seleção de features. - Dependências de UI/lib gráfica (DevExpress) em um worker de console, sem evidência de uso corrente (peso desnecessário no deploy).
2) Meta‑objetivo de arquitetura
Separar Domínio, Aplicação (orquestração) e Infraestrutura para isolar o SDK do instrumento, a camada de exportação e a superfície de rede/arquivos. Alinhar a um Worker Service (ou HostedService) moderno, ainda que o target continue .NET Framework no curto prazo.
consoleEXPORTAR
├─ Application
│ ├─ Orchestrators (CalibrationOrchestrator, ExportOrchestrator)
│ ├─ UseCases (ExportProjectUseCase, MeasureUseCase)
│ └─ Contracts (commands/events/DTOs)
├─ Domain
│ ├─ Entities (Projeto, Medicao, Canal, Faixa)
│ ├─ ValueObjects (InstrumentName, ExportPath)
│ └─ Policies (Regras de calibração, critérios de exportação)
└─ Infrastructure
├─ Instrumentation (IBkEnvApi → Adapter do `BK.BasicEnv.Application`)
├─ Export (IExporter → XmlExporter, CsvExporter, ZipPackaging)
├─ Http (IApiClient via HttpClient)
├─ Config (IOptions/INI provider)
└─ Observability (Logging/Metrics/Tracing)
Padrões: Strategy (formatos de exportação), Adapter (SDK do instrumento), Factory (seleção de features), Template Method/Pipeline (calibrar → medir → exportar), State (estado de medição/calibração).
3) Contratos e DI (exemplos)
public interface IExporter
{
Task ExportAsync(ProjetoContext ctx, CancellationToken ct);
}
public interface IInstrumentApi
{
Task StartCalibrationAsync(string instrument, CancellationToken ct);
Task<CalibrationStatus> GetCalibrationStatusAsync(string instrument, CancellationToken ct);
Task PauseMeasurementAsync(string instrument, CancellationToken ct);
Task ContinueMeasurementAsync(string instrument, CancellationToken ct);
Task ExportProjectToXmlAsync(ExportOptions options, CancellationToken ct);
}
- DI com
Microsoft.Extensions.DependencyInjection(há packages compatíveis com .NET Framework). - Encapsule o SDK do instrumento em
IInstrumentApi(Adapter); evite chamá-lo direto doProgram.
4) Strategy para exportação
public enum ExportFormat { Xml /*, Csv, Json*/ }
public sealed class ExporterFactory
{
private readonly IServiceProvider _sp;
public IExporter Create(ExportFormat fmt) => fmt switch
{
ExportFormat.Xml => _sp.GetRequiredService<XmlExporter>(),
_ => throw new NotSupportedException()
};
}
XmlExporterchama o AdapterIInstrumentApi.ExportProjectToXmlAsync.- Facilita adicionar CSV/JSON sem tocar no resto (OCP).
5) Orquestração assíncrona (sem Thread.Sleep)
Substitua busy waits por wait/poll com timeout e CancellationToken:
public async Task CalibrarEExportarAsync(string instrument, CancellationToken ct)
{
await _inst.StartCalibrationAsync(instrument, ct);
using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
linked.CancelAfter(TimeSpan.FromMinutes(3)); // SLA de calibração
while (!linked.IsCancellationRequested)
{
var st = await _inst.GetCalibrationStatusAsync(instrument, linked.Token);
if (st == CalibrationStatus.Done) break;
await Task.Delay(TimeSpan.FromSeconds(2), linked.Token);
}
await _inst.ContinueMeasurementAsync(instrument, ct);
await _exporter.ExportAsync(/*ctx*/, ct);
}
Benefícios: não bloqueia thread pool, permite cancelamento e SLA claro.
6) Resiliência com Polly
- Retry com backoff e jitter para chamadas do SDK e HTTP.
- Timeout por operação e circuit breaker para isolar falhas repetidas.
var retry = Policy
.Handle<IOException>()
.OrResult<HttpResponseMessage>(r => (int)r.StatusCode >= 500)
.WaitAndRetryAsync(5, i => TimeSpan.FromSeconds(Math.Pow(2, i)) + TimeSpan.FromMilliseconds(Random.Shared.Next(0,250)));
var timeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(30));
var policy = Policy.WrapAsync(retry, timeout);
7) Configuração tipada e validada
- Reunir INI/CONFIG em
IOptions<ExportOptions>; usar providers (háMicrosoft.Extensions.Configuration.Ini). - Validar chaves essenciais (instrumento, diretório de exportação, credenciais, SLA).
- Sem literais no código:
const/enums para nomes de parâmetros (ex.:ParamNames.CICStatus).
public sealed class ExportOptions
{
public string InstrumentName { get; init; }
public string OutputDirectory { get; init; }
public ExportFormat Format { get; init; } = ExportFormat.Xml;
public Uri ApiBaseUrl { get; init; }
public NetworkCredential? BasicAuth { get; init; }
}
8) HTTP seguro e reutilizável
- Use
HttpClientFactory(viaMicrosoft.Extensions.Http) mesmo no .NET Framework. - Configure
DefaultRequestHeadersem DelegatingHandler; não monte Basic em cada chamada. - TLS obrigatório e redação de segredos em logs.
services.AddHttpClient<IApiClient, ApiClient>(c =>
{
c.BaseAddress = opts.ApiBaseUrl;
c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip });
9) Observabilidade (logs estruturados + métricas)
- Serilog com enrichment (
Instrument,Project,ExportFormat,ElapsedMs,Status). - Traces com ActivityId (OpenTelemetry se possível).
- Métricas:
export_success_total,export_failure_total,export_latency_ms(p95),calibration_time_ms.
Exemplo:
_log.ForContext("Instrument", instrument)
.ForContext("Project", projectName)
.Information("Export finished in {ElapsedMs} ms");
10) Clean Code (ações pontuais no Program.cs)
- Extrair classes:
CalibrationOrchestrator,ExportOrchestrator,InstrumentApiAdapter,XmlExporter. - Eliminar
Thread.Sleep→Task.DelaycomCancellationTokene tempo máx. - Remover duplicação ao checar tipos (
"CPB","FFT","RT"): crie coleções e predicados nomeados. - Substituir magic strings por const/enums (
ParamNames,CommandNames). using/await usingpara IDisposable (streams/clients).- Fluxo por intenção: métodos pequenos com nomes descritivos (ex.:
WaitForCalibrationAsync). - Tratamento de exceções: capture apenas o que você consegue tratar; adicione contexto e rethrow se necessário.
- Separar saída de console de lógica; injete
IConsole/ILoggerpara testes. - Analisadores: habilitar
StyleCop/Roslyn +.editorconfig.
11) Estado de medição: State/Enum + tabela de transição
Em vez de if/else para "Measuring", "Paused", "Stopped", modele MeasurementState e centralize a transição (SRP).
public enum MeasurementState { Unknown, Measuring, Paused, Stopped, Calibrating }
Tabela declarativa → reduz ramos e erros.
12) Segurança
- Credenciais fora do INI/CONFIG: User Secrets, variáveis de ambiente ou cofre (Key Vault/DPAPI).
- Basic Auth somente sobre HTTPS; evite logar Authorization.
- Hardening de diretório de exportação: nomes determinísticos, validação de caminho, ACLs mínimas.
13) Empacotamento & distribuição
- Remover dependências não usadas (ex.: DevExpress, se não for necessária).
- Produzir log de dependências (SBOM) e assinar binários se aplicável.
- Script de bootstrap que valida pré‑requisitos do SDK do instrumento.
14) Roadmap de refatoração incremental
Fase 1 (baixo risco):
- Introduza DI + Options e Serilog; mova strings mágicas para const/enums.
- Crie
IInstrumentApi(Adapter) e redirecione chamadas do SDK para a interface. - Substitua
Thread.SleepporTask.Delaycom timeout.
Fase 2 (médio):
- Implemente Strategy/Factory para exportação (XmlExporter).
- Orquestradores assíncronos (calibração/medição/exportação) com CancellationToken.
- Polly para SDK/HTTP (retry/backoff/timeout).
Fase 3 (alto valor):
- Métricas e tracing (OpenTelemetry, se possível).
- Testes de integração com mock do
IInstrumentApi+ fixtures de XML. - Empacotamento leve (trim de dependências) e health check básico.
15) Checklist rápido
-
Program.csreduzido a composition root e bootstrap -
IInstrumentApi(Adapter) +IExporter(Strategy) -
ExportOptionstipado + validação -
HttpClientFactory+ DelegatingHandler p/ auth/headers - Polly (retry/backoff/timeout) em SDK e HTTP
- Logs estruturados + métricas de exportação e calibração
- Remoção de
Thread.Sleep+CancellationTokenem fluxos - Remoção de magic strings (const/enums)
- Testes (unit/integration) com mocks do Adapter
- Dependências revisadas (remover UI libs se não usadas)
16) Snippets úteis
Adapter básico do SDK (esqueleto, simplificado):
public sealed class BkInstrumentApi : IInstrumentApi
{
private readonly IBkSdk _sdk; // wrapper fino do BK.BasicEnv.Application
public Task StartCalibrationAsync(string instrument, CancellationToken ct)
=> _sdk.ExecuteCommandAsync(instrument, CommandNames.StartCicCalibration, ct);
public Task<CalibrationStatus> GetCalibrationStatusAsync(string instrument, CancellationToken ct)
=> _sdk.GetParameterEnumAsync<CalibrationStatus>(instrument, ParamNames.CicStatus, ct);
public Task PauseMeasurementAsync(string instrument, CancellationToken ct)
=> _sdk.ExecuteCommandAsync(instrument, CommandNames.PauseMeasurement, ct);
public Task ContinueMeasurementAsync(string instrument, CancellationToken ct)
=> _sdk.ExecuteCommandAsync(instrument, CommandNames.ContinueMeasurement, ct);
public Task ExportProjectToXmlAsync(ExportOptions options, CancellationToken ct)
=> _sdk.ExportProjectToXmlAsync(options, ct);
}
XmlExporter (Strategy):
public sealed class XmlExporter : IExporter
{
private readonly IInstrumentApi _api;
private readonly ILogger _log;
private readonly IOptions<ExportOptions> _opts;
public async Task ExportAsync(ProjetoContext ctx, CancellationToken ct)
{
var sw = Stopwatch.StartNew();
await _api.ExportProjectToXmlAsync(_opts.Value, ct);
_log.Information("Export XML concluído em {ElapsedMs} ms", sw.ElapsedMilliseconds);
}
}
Resultado esperado
- Redução drástica do acoplamento e aumento de testabilidade.
- Operação mais estável (timeouts/retries) e observável (logs/métricas).
- Evolução facilitada (novos formatos, novos instrumentos) com baixo impacto.