Gehirn-Überentwicklung

Ich bin auf eine einfache unterhaltsame Aufgabe gestoßen: Sammeln Sie Daten zur Temperatur von Wasser und Luft von einigen HTML-Seiten und geben Sie das Ergebnis in JSON von der API zurück. Die Aufgabe ist trivial, sie wird durch einen Zeilencode von 40 (oder so) mit Kommentaren gelöst. Natürlich, wenn Sie nach dem Quick & Dirty-Prinzip schreiben. Dann stinkt der geschriebene Code und entspricht nicht den modernen Programmierstandards.






Nehmen wir eine einfache Ausführung als Grundlage und sehen, was passiert, wenn wir sie umgestalten ( Code mit Commits ).





public async Task<ActionResult<MeasurementSet>>
        Get([FromQuery]DateTime? from = null, 
            [FromQuery]DateTime? to = null)
{
	// Defaulting values
  from ??= DateTime.Today.AddDays(-1);
  to ??= DateTime.Today;
  // Defining URLs
  var query = $"beginn={from:dd.MM.yyyy}&ende={to:dd.MM.yyyy}";
  var baseUrl = new Uri("https://BaseURL/");
  using (var client = new HttpClient { BaseAddress = baseUrl })
  {
    // Collecting data
    return Ok(new MeasurementSet 
    { 
      Temperature = await GetMeasures(query, client, "wassertemperatur"), 
      Level = await GetMeasures(query, client, "wasserstand"), 
    });
  }
}

private static async Task<IEnumerable<Measurement>>
        GetMeasures(string query, HttpClient client, string apiName)
{
  // Retrieving the data
  var response = await client.GetAsync($"{apiName}/some/other/url/part/?{query}");
  var html = await response.Content.ReadAsStringAsync();
  // Parsing HTML response
  var bodyMatch = Regex.Match(html, "<tbody>(.*)<\\/tbody>");
  var rowsHtml = bodyMatch.Groups.Values.Last();
  return Regex.Matches(rowsHtml.Value, "<tr  class=\"row2?\"><td >([^<]*)<\\/td><td  class=\"whocares\">([^<]*)<\\/td>")
    // Building the results
    .Select(match => new Measurement
    {
      Date = DateTime.Parse(match.Groups[1].Value),
      Value = decimal.Parse(match.Groups[2].Value)
    });
}
      
      







1. Fehlerbehandlung





- , .





if (response.IsSuccessStatusCode)
{
  throw new Exception($"{apiName} gathering failed with. [{response.StatusCode}] {html}");
}
// Parsing HTML response
var bodyMatch = Regex.Match(html, "<tbody>(.*)<\\/tbody>");
if (!bodyMatch.Success)
{
  throw new Exception($"Failed to define data table body. Content: {html}");
}
      
      



2.





, , , . , MeasureParser RawMeasuresCollector . , , .





3.





enum, , :





var apiName = measure switch
{
  MeasureType.Temperature => "wassertemperatur",
  MeasureType.Level => "wasserstand",
  _ => throw new NotImplementedException($"Measure type {measure} not implemented")
};
      
      



, . :





public class UrlQueryBuilder
{
  public DateTime From { get; set; } = DateTime.Today.AddDays(-1);
  public DateTime To { get; set; } = DateTime.Today;

  public string Build(MeasureType measure)
  {
    var query = $"beginn={From:dd.MM.yyyy}&ende={To:dd.MM.yyyy}";
    var apiName = measure switch
    {
      MeasureType.Temperature => "wassertemperatur",
      MeasureType.Level => "wasserstand",
      _ => throw new NotImplementedException($"Measure type {measure} not implemented")
    };
    return $"{apiName}/some/other/url/part/?{query}";
  }
}
      
      



4. (coupling)





. , , . URL :





var settings = Configuration.GetSection("AppSettings").Get<AppSettings>();
services.AddHttpClient(Constants.ClientName, client =>
{
  client.BaseAddress = new Uri(settings.BaseUrl);
});
services.AddTransient<IRawMeasuresCollector, RawMeasuresCollector>();
      
      



5.





. , :





    [TestMethod]
public async Task TestHtmlTemperatureParsing()
{
  var collector = new Mock<IRawMeasuresCollector>();
  collector
    .Setup(c => c.CollectRawMeasurement(MeasureType.Temperature))
    .Returns(Task.FromResult(_temperatureDataA));

  var actual = (await new MeasureParser(collector.Object)
    .GetMeasures(MeasureType.Temperature)
    ).ToArray();

  Assert.AreEqual(165, actual.Length);
  Assert.AreEqual(7.1M, actual
      .First(l => l.Date == DateTime.Parse("24.11.2020 10:15")).Value);
}
      
      



6.





, , . DOM , . .





:





public async Task<ActionResult<MeasurementSet>> Get
    ([FromQuery] DateTime? from = null, [FromQuery] DateTime? to = null)
{
  var parser = new MeasureParser(_collectorFactory.CreateCollector(from, to));
  return Ok(new MeasurementSet
  {
    Temperature = await parser.GetTemperature(),
    Level = await parser.GetLevel(),
  });
}
      
      



Die vorherigen Korrekturen führten zur Nutzlosigkeit der Aufzählung mit dem Dimensionstyp, und wir mussten den in Punkt 3 angegebenen Code entfernen, was ziemlich positiv ist, da er den Grad der Codeverzweigung verringert und zur Vermeidung von Fehlern beiträgt.





Ergebnis

Infolgedessen hat sich die Ein-Seiten-Methode zu einem anständigen Projekt entwickelt





Natürlich ist das Projekt flexibel, unterstützt usw. usw. Aber es besteht das Gefühl, dass es zu groß ist. Laut VS Analytics sind von 277 Codezeilen nur 67 ausführbar.





Möglicherweise ist das Beispiel nicht korrekt, da die Funktionalität nicht so umfassend ist oder das Refactoring falsch und nicht vollständig durchgeführt wird.





Teile deine Erfahrung.








All Articles