Continuo a rimbalzare da "God function" a "minuscole funzioni SRP" come posso interrompere questo ciclo?

3

Questa domanda è importante per me nella crescita delle mie capacità tecniche. Trovo che oscilli da capo a punto, come un pendolo, scrivendo un codice che è allo stesso tempo ASCIUTTO ma leggibile & efficiente. E lo faccio costantemente ... il che porta a troppi fattori di ri-determinazione e mai a una risoluzione soddisfacente.

Il vero esempio è un carrello di e-commerce, in cui hai due azioni di base: add_item e remove_item . Entrambe le azioni eseguono le seguenti operazioni, regolano un item_counter utilizzato per calcolare un total_price (che viene mostrato nella vista), aggiungi / rimuovi un cart_item nella vista.

Inizialmente, ho cercato il codice più semplificato possibile, quindi ho finito per sembrare:

$(".item").click(function() {
  type_of_operation = $(this).data("type") // can be add or remove
  item_id = $(this).data("id")
  doOperation(type_of_operation, item_id)
})

function doOperation(type_of_operation, item_id) {
  count = toggleItemCounter(type_of_operation)
  updatePrice(count)
  toggleCartItem(type_of_operation, item_id)
}

function toggleItemCounter(type_of_operation) {
  old_count = JSON.parse(sessionStorage.getItem("counter")
  if (type_of_operation == "add") {
    new_count = old_count + 1
  } else if (type_of_operation == "remove") {
    new_count = old_count - 1
  }
  return new_count
}

function updatePrice(count) {
  price = count * 10
  $("#total_price").text(price)
}

function toggleCartItem(type_of_operation, item_id) {
  if (type_of_operation == "add") {
    item = "<div class='item' data-type='remove' data-id='item_id'></div>"
    $("#item-list").append(item)
  } else if (type_of_operation == "remove") {
    $("#item-list").find("[data-id='"+item_id+"']").remove()
  }
}

Man mano che la base di codice cresceva, mi sono reso conto che ci sono alcune attività eccezionali per aggiungere / rimuovere e alcuni item_ids, quindi una funzione, ad esempio, è cresciuta in qualcosa del genere:

function doOperation(type_of_operation, item_id) {
  if (item_id = 999) {
    specialPriceUpdate()
  } else {
    if (type_of_operation == "modify") {
      adjustCartItem(item_id)
    } else {
      count = toggleItemCounter(type_of_operation)
      updatePrice(count)
      toggleCartItem(type_of_operation, item_id)
    }
  }
}

Questa crescita continuò come un tumore fino a quando doOperation divenne ingombrante e molto difficile da leggere, con nidi di condizionali e ritorni speciali e così via. Mi resi conto che la mia rovina era che cercavo di rendere tutto "generico" quando avrei dovuto essere più specifico. Quindi, in un refactoring, ho riscritto quanto sopra a qualcosa del genere:

$(".add_item").click(function() {
  item_id = $(this).data("id")
  count = addItemCounter()
  updatePrice(count)
  addCartItem(item_id)
}

$(".remove_item").click(function() {
  item_id = $(this).data("id")
  count = reduceItemCounter()
  updatePrice(count)
  removeCartItem(item_id)
}

$(".modify_item").click(function() {
  item_id = $(this).data("id")
  adjustCartItem(item_id)
}

$(".special_item") {
  specialPriceUpdate()
}

function addItemCounter() {
  old_count = JSON.parse(sessionStorage.getItem("counter")
  return old_count + 1
}

function reduceItemCounter() {
  old_count = JSON.parse(sessionStorage.getItem("counter")
  return old_count - 1
}

function updatePrice(count) {
  // same as previous
}

function addCartItem(item_id) {
  item = "<div class='item' data-type='remove' data-id='item_id'></div>"
  $("#item-list").append(item)
}

function removeCartItem(item_id) {
  $("#item-list").find("[data-id='"+item_id+"']").remove()
}

function adjustCartItem(item_id) {
  $("#item-list").find("[data-id='"+item_id+"']").append("some adjustment")
}

E poi ho pensato, ok che sembra più chiaro e leggibile. Ma poi (perché il vero esempio ha più funzioni), ero come ... beh, aspetta un minuto, in genere segui sempre lo stesso schema: aggiungendo o rimuovendo il contatore, aggiornando il prezzo, quindi aggiungendo o rimuovendo l'oggetto, quindi forse dovrebbe esserci un% co_de unificato e una funzione add unificata, quindi qualcosa come ...

function addItem(item_id) {
  count = addItemCounter()
  updatePrice(count)
  addCartItem(item_id)
}

function removeItem(item_id) {
  count = reduceItemCounter()
  updatePrice(count)
  removeCartItem(item_id)
}

E conoscendo me stesso, una volta che sarò a quel punto, lo guarderò e penso, per gol remove e addItem sono fondamentalmente uguali, tranne che per l'aggiunta e la rimozione di uno! Perché non lo collasso semplicemente in una singola funzione con una serie di interruttori e quindi alcuni condizionali per consentire le eccezioni? Sì, e poi questo mi riporta al punto 1 ...

Vedi, a differenza di un pendolo, non smetto mai di oscillare da una parte all'altra per raggiungere una sorta di buona via di mezzo. Letteralmente potrei girare e girare in tondo ogni volta che mi siedo per riesaminare il codice. La mia mente è fondamentalmente come ... "le grandi funzioni di dio sono più efficienti ... per ... non c'è modo, hanno bisogno di funzioni ben distinte per tutto ... per ... hmm sembra che queste funzioni distinte potrebbero essere un po 'più secche, asciugiamole .. per ... ok bene li ho asciugati ancora una volta in una grande funzione di dio ... "

Penso che parte di esso sia che non sono sicuro di attenermi a una soluzione, perché c'è così tanto contesto su ciò che è "ottimale". In parte è che sono autodidatta, quindi non ho i principi guida per dire veramente, OK smetti di oscillare, questa è la regola pratica appropriata da seguire.

Questo / è successo a te? Cosa fai ?? !!

    
posta james 27.07.2017 - 02:10
fonte

2 risposte

5

Ciò di cui hai bisogno è una nuova astrazione.

Semplicemente estraendo i bit comuni di funzionalità si finisce per creare un pasticcio. Devi trovare un modo nuovo di guardare il tuo problema che ti consenta di estrarre le funzionalità comuni senza creare problemi.

È più facile a dirsi che a farsi!

Per questo caso particolare, un'idea migliore è quella di dividere la logica di aggiornamento della dom dalla logica di aggiornamento dello stato. Qualcosa del genere:

function updateApp(updater) {
    // fetch javascript object representing current state
    var currentState = JSON.parse(sessionStorage.getItem("appState"));
    // allow the passed in function to update that state
    updater(currentState);
    // save the state
    sessionStorage.setItem("appState", JSON.stringify(currentState));
    // update the user interface.
    // the vhtml function update the #item-list to correspond
    // to the html described by renderItems
    $('#item-list').vhtml(renderItems(state));
}

// this function returns a data structure that describes the html that should
// be rendered. 
function renderItems(state) {
    return h("div",
        state.items.map((item) => h("div.item", {data_id: item.id},
            "Price: " + item.price * item.qty)));
}

// when you want to modify the state, do it through the updateState
// function. Here we have the relatively straightforward task of
// just tweaking the data structure holding the state to include
// the extra item.
$(".add_item").click(function() {
  item_id = $(this).data("id")
  updateApp(function(state) {
      state.items.push({
          id: item_id
      });
  })
}

Questo nuovo abstract cambia il tuo modo di pensare al tuo codice. Prima dovevi preoccuparti di tutti i pezzi di DOM che dovevano essere aggiornati. Ora, dividi l'attività in due:

  1. Per le tue funzioni evento, aggiorna l'oggetto stato in risposta all'azione
  2. Per la funzione di rendering, basta restituire i nodi DOM corretti per lo stato corrente.

Ora semplicemente non è necessario scrivere il codice che illustra le modifiche corrette da apportare al DOM. Questo è gestito per te. Si spera che, se si aggiungono altre funzioni che modificano il dom, non sarà necessario aggiungere molta complessità per gestire tutti questi casi. Funzionerà.

Si potrebbe pensare che sarebbe inefficiente ricostruire semplicemente il DOM ogni volta per la funzione di rendering. Sì, lo sarebbe. Ma ci sono un certo numero di dom dom virtuali che lo rendono efficiente. Potrebbe o non potrebbe valere la pena aggiungere al tuo progetto.

Qualunque sia il merito del tuo caso particolare, questa è la soluzione generale al tuo problema: trovare un modo migliore per dividere il tuo problema. Il motivo per cui non ha funzionato per creare più funzioni generiche per te è che non hai trovato il modo ideale per dividere il tuo compito.

    
risposta data 27.07.2017 - 03:53
fonte
1

In che modo il codice varierà in futuro? Quale codice sarà più facile da aggiungere?

Se si hanno molte eccezioni per elemento, non si desidera una funzione completa per ogni caso ( priceUpdate , specialPriceUpdate , anotherUpdateFunction , ...). Avrai bisogno di una funzione che può variare il calcolo in base ai parametri.

D'altra parte, se cambierai il modo in cui vengono contati gli elementi, potresti volere una funzione updateCount che includa un delta.

In alternativa, se aggiungerai molte regolazioni diverse (ad esempio colore, dimensione, stile, ecc.), potresti voler cambiare il modo in cui sono rappresentate.

Credo che la mia risposta sia che va bene avere una duplicazione strutturale limitata. Aggiungere e rimuovere un oggetto sono significativamente diversi, e dubito che otterrai qualcos'altro che userebbe lo stesso schema. Se stai facendo un'astrazione e non riesci a pensare a nessun altro, verrebbe usato, quindi fermati.

Inoltre, come un consiglio concreto, avere una funzione getItemCount per incapsulare JSON.parse(sessionStorage.getItem("counter")) eliminerebbe quella duplicazione, e quindi addItem e removeItem possono semplicemente utilizzare addizione e sottrazione. Posso immaginare un sacco di volte in cui vorresti ottenere il conteggio degli oggetti (come in updatePrice ).

    
risposta data 27.07.2017 - 03:07
fonte

Leggi altre domande sui tag