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 (SDK BK.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 com Thread.Sleep e muitos if/elses.
  • Baixa testabilidade: uso predominante de static, singletons implícitos do SDK, acesso direto a DateTime.Now, File.*, Console.* e Thread.Sleep.
  • Resiliência frágil: sem timeouts ou retries consistentes; polling com Sleep em 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 do Program.

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()
    };
}
  • XmlExporter chama o Adapter IInstrumentApi.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 (via Microsoft.Extensions.Http) mesmo no .NET Framework.
  • Configure DefaultRequestHeaders em 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)

  1. Extrair classes: CalibrationOrchestrator, ExportOrchestrator, InstrumentApiAdapter, XmlExporter.
  2. Eliminar Thread.SleepTask.Delay com CancellationToken e tempo máx.
  3. Remover duplicação ao checar tipos ("CPB", "FFT", "RT"): crie coleções e predicados nomeados.
  4. Substituir magic strings por const/enums (ParamNames, CommandNames).
  5. using/await using para IDisposable (streams/clients).
  6. Fluxo por intenção: métodos pequenos com nomes descritivos (ex.: WaitForCalibrationAsync).
  7. Tratamento de exceções: capture apenas o que você consegue tratar; adicione contexto e rethrow se necessário.
  8. Separar saída de console de lógica; injete IConsole/ILogger para testes.
  9. 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.Sleep por Task.Delay com 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.cs reduzido a composition root e bootstrap
  • IInstrumentApi (Adapter) + IExporter (Strategy)
  • ExportOptions tipado + 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 + CancellationToken em 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.