Fallo in due cicli

Dec 30 2022
Provenendo da un background informatico, sospetto che la parola "efficienza" debba essere già una parte del mio cervello da qualche parte. Tutti quegli alberi che ho attraversato, i grandi O e gli omega erano lì per aiutarmi ad adottare l'approccio più efficiente.
Prompt di diffusione stabile: fumetto minimalista di due loop con colori piatti e rettangoli sparsi in modo casuale

Provenendo da un background informatico, sospetto che la parola "efficienza" debba essere già una parte del mio cervello da qualche parte. Tutti quegli alberi che ho attraversato, i grandi O e gli omega erano lì per aiutarmi ad adottare l'approccio più efficiente. E sì, a volte sono utili e sono orgoglioso delle domande di CP che posso risolvere con loro. Se ti trovi nello spazio dell'ingegneria del prodotto come me, però, forse condividerai la mia intuizione secondo cui la nozione di efficienza nell'ingegneria del prodotto non è quella che abbiamo imparato al college. Ho fatto i conti con questa divergenza. Dopotutto, il mio preside della scuola elementare si è laureato in ingegneria aerospaziale e ne ha fatto il miglior uso.

Uno degli schemi notevoli che ho usato per scrivere nel codice di logica aziendale che desidera l'efficienza è lo schema di fare tutto in un ciclo. Di tanto in tanto mi imbatto anche in altri ingegneri, di solito grazie a contributi all'inizio della loro carriera.

Ecco un pezzo di codice che calcola i voti medi di un gruppo di studenti in diversi semestri, quindi restituisce una mappa hash con ID studente come chiavi:

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

Innanzitutto, abbiamo una netta separazione tra la parte di codice che recupera i dati e la parte di codice che esegue il calcolo. Con un semplice refactoring del metodo di estrazione, la nostra funzione principale potrebbe ora apparire semplicemente così:

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

Un altro vantaggio è che ho maggiori probabilità di individuare ottimizzazioni molto più significative quando il mio codice è facile da leggere. Nell'esempio precedente, ad esempio, diventa più semplice eseguire il refactoring della parte che richiede i voti degli studenti raggruppando la richiesta. Dobbiamo semplicemente eseguire il refactoring getStudentGradesForAllSemesterssenza preoccuparci che altre parti della logica aziendale siano interessate!

Per i motivi di cui sopra, vorrei raccomandare di fare due cicli anche quando puoi farli entrambi in uno. Cioè, favorire la separazione logica rispetto a banali efficienze. Se ti trovi in ​​​​uno spazio di ingegneria del prodotto simile come me, renderà il tuo lavoro più semplice quando avrai un codice facile da estendere logicamente!

Tocca a te: hai già incontrato questo tipo di loop do-it-all-in-one-go for? Forse lo trovavi davvero bene? Discutiamone :)