Faça isso em dois loops

Dec 30 2022
Vindo de uma formação em Ciência da Computação, suspeito que a palavra 'eficiência' já deva fazer parte do meu cérebro em algum lugar. Todas aquelas árvores que atravessei, os grandes Os e os ômegas estavam lá para me ajudar a fazer uma abordagem mais eficiente.
Prompt de difusão estável: desenho minimalista de dois loops com cores planas e retângulos dispersos aleatoriamente

Vindo de uma formação em Ciência da Computação, suspeito que a palavra 'eficiência' já deva fazer parte do meu cérebro em algum lugar. Todas aquelas árvores que atravessei, os grandes Os e os ômegas estavam lá para me ajudar a fazer uma abordagem mais eficiente. E sim - eles são úteis às vezes, e tenho orgulho das questões de CP que posso resolver com eles. Se você se encontra no espaço da engenharia de produto como eu, talvez compartilhe minha visão de que a noção de eficiência na engenharia de produto não é aquela que aprendemos na faculdade. Eu cheguei a um acordo com essa divergência. Afinal, meu diretor de escola primária se formou em engenharia aeroespacial e fez o melhor uso possível.

Um dos padrões notáveis ​​que usei para escrever no código de lógica de negócios que anseia por eficiência é o padrão de fazer tudo em um loop. Ocasionalmente, deparo-me com isso de outros engenheiros também, geralmente de contribuições no início de suas carreiras.

Aqui está um trecho de código que calcula as notas médias de um grupo de alunos em vários semestres e, em seguida, retorna um mapa de hash com IDs de alunos como chaves:

avgFinalGradesByStudent := make(map[studentID]float)
for _, student := range students {
  totalFinalGrades := 0.0
  for _, semester := range semesters {
    studentGrade := gradesRepository.getByStudentID(student.ID, semester) // fetch from db
    totalFinalGrades = totalFinalGrades + studentGrade.FinalGrade
  }
  avgFinalGradesByStudent[student.ID] := totalFinalGrades / len(semesters)
}

return avgFinalGradesByStudent

studentGrades := make(map[studentID][]float)
for _, student := range students {
  for _, semester := range semesters {
    semesterGrade := gradesRepository.getByStudentID(student.ID, semester) // fetch from db
    studentGrades[student.ID] = append(studentGrades[student.ID], semesterGrade)
  }
}

avgFinalGradesByStudent := make(map[studentID]int)
for _, student := range students {
  totalFinalGrades := 0
  for _, grade := range studentGrades[studentID] {
    totalFinalGrades += grade
  }
  avgFinalGradesByStudent[student.ID] := totalFinalGrades / len(semesters)
}

return avgFinalGradesByStudent

Primeiro, temos uma separação clara entre a parte do código que busca dados e a parte do código que executa a computação. Com uma simples refatoração do método de extração, nossa função principal pode ficar simplesmente assim agora:

studentGrades := getStudentGradesForAllSemesters(students, semesters)
avgFinalGradesByStudent := calculateAverageFinalGrades(studentGrades)

return avgFinalGradesByStudent

// arrange
studentGrades := map[studentID][]int { 1: []int {97, 86, 51}, 2: []int {60, 85, 95} } 
// act
result := calculateAverageFinalGrades(studentGrades)
// assert
assert.Equal(78,result[1])
assert.Equal(80, result[2])

avgFinalGradesByStudent := make(map[studentID]float)
// container for the final results
topThreeGradesByStudent := make(map[studentID][]int)
for _, student := range students {
  totalFinalGrades := 0.0
  var topThreeGrades []int
  for _, semester := range semesters {
    studentGrade := gradesRepository.getByStudentID(student.ID, semester) // fetch from db
    totalFinalGrades = totalFinalGrades + studentGrade.FinalGrade
    // some code to figure out if this grade is the top three grades or not 
    // and add/remove from topThreeGrades
    // ...
  }
  avgFinalGradesByStudent[student.ID] := totalFinalGrades / len(semesters)
  topThreeGradesByStudent[student.ID] := topThreeGrades
}

return avgFinalGradesByStudent, topThreeGradesByStudent

studentGrades := getStudentGradesForAllSemesters(students, semesters)
avgFinalGradesByStudent := calculateAverageFinalGrades(studentGrades)
topThreeGradesByStudent := getTopThreeGradesByStudent(studentGrades)

return avgFinalGradesByStudent, topThreeGradesByStudent

Outra vantagem é que acho que tenho mais chances de detectar otimizações muito mais significativas quando meu código é fácil de ler. No exemplo acima, por exemplo, torna-se mais fácil refatorar a parte que consulta as notas dos alunos agrupando a solicitação. Simplesmente precisamos refatorar getStudentGradesForAllSemesterssem nos preocuparmos com a possibilidade de outras partes da lógica de negócios serem afetadas!

Pelas razões acima, gostaria de recomendar fazer dois loops, mesmo quando você pode fazer os dois em um. Ou seja, favoreça a separação lógica em vez de eficiências triviais. Se você estiver em um espaço de engenharia de produto semelhante ao meu, seu trabalho ficará mais fácil quando você tiver um código fácil de estender logicamente!

Para você - você já encontrou esse tipo de loops do tipo "faça tudo de uma vez" antes? Talvez você realmente tenha achado isso bom? Vamos discutir :)