Rekayasa otak yang berlebihan

Saya mendapat tugas hiburan sederhana: mengumpulkan data tentang suhu air dan udara dari beberapa halaman HTML dan mengembalikan hasilnya ke JSON dari API. Tugasnya sepele, itu diselesaikan dengan kode baris 40 (atau lebih) dengan komentar. Tentunya jika menulis berpedoman pada prinsip Quick & Dirty. Maka kode yang tertulis akan berbau dan tidak sesuai dengan standar pemrograman modern.






Mari kita ambil eksekusi sederhana sebagai dasar dan lihat apa yang terjadi jika kita memfaktor ulangnya ( kode dengan komit )





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. Penanganan kesalahan





- , .





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(),
  });
}
      
      



Perbaikan sebelumnya menyebabkan ketidakbergunaan enum dengan tipe dimensi, dan kami harus menyingkirkan kode yang ditentukan di poin 3, yang agak positif, karena mengurangi tingkat percabangan kode dan membantu menghindari kesalahan.





Hasil

Hasilnya, metode satu halaman telah berkembang menjadi proyek yang layak





Tentu saja, proyek ini fleksibel, didukung, dll., Dll. Tetapi ada perasaan bahwa proyek itu terlalu besar. Menurut analisis VS, dari 277 baris kode, hanya 67 yang dapat dieksekusi.





Mungkin contohnya tidak benar, karena fungsinya tidak begitu luas, atau pemfaktoran ulang dilakukan secara tidak benar, tidak sepenuhnya.





Bagikan pengalaman Anda.








All Articles