JavaRush /Java Blog /Random-IT /Pausa caffè #64. Come scrivere codice pulito. Perché Java...

Pausa caffè #64. Come scrivere codice pulito. Perché Java è migliore di C++ per i sistemi a bassa latenza

Pubblicato nel gruppo Random-IT

Come scrivere codice pulito

Fonte: Dev.to Scrivere codice pulito è come scrivere poesie. Questa è poesia che dovrebbe essere concisa, comprensibile e accessibile al cambiamento. Il codice pulito implica un'organizzazione scalabile. Ciò significa che apportare modifiche non causa caos. La capacità di scrivere tale codice è una delle qualità chiave di uno sviluppatore esperto. Dopo che diverse persone mi hanno consigliato di leggere il libro Clean Code, ho finalmente trovato il coraggio di leggerlo. Si è scoperto che questo è uno di quei libri in cui la copertina è completamente all'altezza dell'hype che lo circonda. Le raccomandazioni contenute nel libro sono chiare, specifiche, pratiche e persino presentate con umorismo. Oggi voglio condividere con voi i principali insegnamenti tratti da questo libro.Pausa caffè #64.  Come scrivere codice pulito.  Perché Java è migliore di C++ per i sistemi a bassa latenza - 1

1. Il codice non dovrebbe solo funzionare, ma anche essere leggibile

La maggior parte del costo del software è legato al supporto a lungo termine. Pertanto, il codice che scrivi deve esprimere chiaramente le tue intenzioni. Dovrebbe essere tale che i nuovi sviluppatori che si uniscono al team possano facilmente capire cosa sta succedendo esattamente nel codice e perché. Quanto più comprensibile è il codice scritto dall'autore, tanto meno tempo occorrerà agli altri sviluppatori per capirlo. Ciò riduce i difetti e i costi di manutenzione. Come raggiungere questo obiettivo? Buona denominazione + classi e funzioni con singola responsabilità + prove di scrittura.

2. Più tardi significa mai

Siamo onesti: a volte promettiamo a noi stessi che torneremo e ripuliremo il codice più tardi, ma finiamo per dimenticarcene. Non lasciare pezzi di codice inutili che non servono più. Confondono gli altri sviluppatori e non hanno alcun valore. Pertanto, quando si apportano modifiche alla funzionalità, rimuovere sempre il vecchio codice. Se qualcosa si rompe da qualche parte, i test lo dimostreranno comunque subito. Come raggiungere questo obiettivo? L'eliminazione del codice può essere spaventosa, soprattutto nelle architetture di grandi dimensioni. Quindi il test è fondamentale qui. Ti consentono di rimuovere il codice in tutta sicurezza.

3. Le funzionalità dovrebbero essere piccole

La prima regola per scrivere le funzioni è che dovrebbero essere piccole, fino a circa 20 righe . Più piccola è la funzione e più focalizzata su un compito, più facile sarà trovarne un buon nome. Per quanto riguarda gli argomenti delle funzioni, il loro numero ideale è 0. Poi vengono 1, 2, ma dovresti cercare di non avere più di 3 argomenti. Come ottenere questo? Le funzioni dovrebbero essere scritte in conformità con i principi di responsabilità unica e aperta/chiusa.

4. La duplicazione del codice è dannosa

La duplicazione è nemica di un sistema ben organizzato. È lavoro extra, rischio extra e complessità extra inutile. Cosa fare al riguardo? Assicurati che il tuo codice sia scritto secondo il principio DRY, isolato e modulare.

5. L'unico commento buono è quello in cui hai trovato il modo di non scrivere.

“Non c’è niente di più utile di un buon commento nel posto giusto. Ma i commenti, anche nella migliore delle ipotesi, sono un male necessario”. I commenti hanno lo scopo di compensare la nostra incapacità di esprimere i nostri pensieri nel codice. Cioè, questa è inizialmente un'ammissione di sconfitta. Sì, dobbiamo usarli perché non sempre riusciamo a chiarire le nostre intenzioni con il codice, ma non c'è motivo di festeggiare. Il fatto è che i commenti spesso mentono. Non sempre e non di proposito, ma troppo spesso. Più il commento è vecchio e più è lontano dal codice che descrive, più è probabile che sia errato. La ragione di ciò è semplice: i programmatori non riescono a mantenere molto bene sia il codice che tutti i commenti. Molto spesso, quindi, i commenti vengono separati dal codice a cui si riferiscono e diventano annotazioni orfane con una precisione minima. Cosa fare al riguardo? È necessario utilizzare metodi di denominazione descrittivi. Quando leggi il nome di una variabile dovresti capire subito di cosa si tratta. Sono inoltre necessari test in modo che altri sviluppatori comprendano quale funzionalità è più importante.

6. L'oggetto rivela il comportamento, ma non i dati.

Un modulo non dovrebbe conoscere le parti interne degli oggetti che manipola. Gli oggetti nascondono i loro dati e rivelano le loro operazioni. Ciò significa che un oggetto non dovrebbe esporre la sua struttura interna tramite metodi di accesso. Non è necessario che tutti ti vedano nudo. Cosa fare al riguardo? L'ambito delle variabili dovrebbe essere il più locale possibile in modo da non esporre più del necessario.

7. Test

Il codice di test è importante tanto quanto ciò che entra in produzione. Pertanto deve cambiare e crescere man mano che il progetto si sviluppa. I test mantengono il codice flessibile, gestibile e riutilizzabile. Senza di essi, qualsiasi modifica potrebbe causare bug. I test ti consentono di pulire il tuo codice senza temere che qualcosa si rompa. Pertanto, mantenere la purezza dei test è di grande importanza. La pulizia dei test ne garantisce la leggibilità. I test sono un'opportunità per spiegare ad altri sviluppatori con un linguaggio semplice le intenzioni dell'autore del codice. Pertanto, testiamo solo un concetto in ciascuna funzione di test. Ciò rende il test descrittivo, più facile da leggere e, se fallisce, è più facile rintracciarne il motivo. Come raggiungere questo obiettivo? È necessario seguire i principi dei PRIMI test puliti . I test dovrebbero essere:
  • Veloce. I test devono svolgersi rapidamente. Se devi aspettare troppo a lungo prima che un test venga eseguito, è meno probabile che lo eseguirai più spesso.
  • Indipendente/isolato (Independent). I test dovrebbero essere il più possibile isolati e indipendenti l’uno dall’altro.
  • Ripetibile. I test dovrebbero essere ripetibili in qualsiasi ambiente: sviluppo, staging e produzione.
  • Autoconvalida. Il risultato del test deve essere un valore booleano. Il test deve essere superato o fallito.
  • Completo. Dovremmo sforzarci di coprire tutti i casi limite, tutti i problemi di sicurezza, ogni caso d'uso (caso d'uso) e percorso felice (lo scenario più favorevole per il codice) con i test.

8. Gestione degli errori e delle eccezioni

Ogni eccezione generata dovrebbe fornire un contesto sufficiente per determinare l'origine e la posizione dell'errore. In genere si dispone di un'analisi dello stack di qualsiasi eccezione, ma un'analisi dello stack non indica lo scopo dell'operazione non riuscita. Se possibile, evita di passare null nel codice. Se sei tentato di restituire null da un metodo, considera invece di lanciare un'eccezione. Rendere la gestione degli errori un'attività separata che può essere visualizzata indipendentemente dalla logica principale. Come raggiungere questo obiettivo? Crea messaggi di errore informativi e trasmettili insieme alle tue eccezioni. Specificare l'operazione non riuscita e il tipo di errore.

9. Classi

Le classi dovrebbero essere piccole. Ma non sono le righe di codice a dover essere contate, ma la responsabilità. I nomi delle classi sono fondamentali per descrivere di cosa sono responsabili. I nostri sistemi dovrebbero essere costituiti da molte piccole classi, non da poche grandi. Ciascuna classe così piccola deve incapsulare una singola responsabilità. Deve esserci solo una ragione specifica per l'esistenza di ciascuna classe e ciascuna classe deve "cooperare" con diverse altre classi per ottenere il comportamento desiderato del sistema. Raramente esiste una buona ragione per creare una variabile pubblica. L’indebolimento dell’incapsulamento è sempre l’ultima risorsa. Inoltre, dovrebbero esserci poche variabili di istanza. Una buona progettazione del software consente di apportare modifiche senza grandi investimenti o rielaborazioni. Restringere la gamma delle variabili rende questo compito più semplice. Come raggiungere questo obiettivo? La separazione degli interessi è una delle tecniche di progettazione più antiche e importanti. Le lezioni dovrebbero essere aperte per l'estensione, ma chiuse per la modifica. In un sistema ideale, abilitiamo nuove funzionalità estendendo il sistema anziché apportando modifiche al codice esistente.

10. Formattazione

Ogni riga vuota è un segnale visivo che aiuta a identificare l'inizio di un concetto nuovo e separato. Le variabili locali devono apparire nella parte superiore della funzione. Le variabili di istanza devono essere dichiarate all'inizio della classe. Le linee brevi sono migliori di quelle lunghe. Di solito il limite è di 100-120 caratteri; non dovresti allungarlo. Come raggiungere questo obiettivo? La maggior parte dei parametri può essere passata a un linter nel CI o nell'editor di testo. Usa questi strumenti per rendere il tuo codice il più pulito possibile.

Principi di sviluppo del programma

Utilizza le seguenti tecniche e il tuo codice sarà sempre pulito: Denominazione delle variabili. La scelta dei nomi appropriati (buona denominazione) è fondamentale per rendere il codice leggibile e quindi manutenibile. "Dovresti scegliere un nome per una variabile con la stessa responsabilità che faresti per il tuo primogenito." Scegliere buoni nomi è spesso una sfida per gli sviluppatori. Ciò richiede buone capacità descrittive e un background culturale condiviso. Il codice pulito è codice che viene letto e migliorato da sviluppatori completamente diversi. Il nome di una variabile, funzione o classe dovrebbe rispondere a tutte le domande di base: perché esiste questa entità, cosa e come viene utilizzata. Se un nome necessita di commento, significa che non rivela sufficientemente l'essenza di ciò che descrive. I nomi più lunghi sono più importanti di quelli più brevi e qualsiasi nome ricercabile è migliore di una costante. I nomi composti da una sola lettera possono essere utilizzati solo come variabili locali all'interno di metodi brevi: la lunghezza del nome deve corrispondere allo scope. I nomi dei metodi devono essere verbi o frasi verbali; il nome della classe non deve essere un verbo. Le dipendenze dovrebbero essere ridotte al minimo. È meglio fare affidamento su ciò che controlli piuttosto che su ciò che non puoi controllare. Altrimenti queste cose ti controlleranno. Precisione. Ogni singolo pezzo di codice dovrebbe trovarsi nel posto in cui il lettore si aspetta di trovarlo. La navigazione nella codebase dovrebbe essere intuitiva e le intenzioni dello sviluppatore dovrebbero essere chiare. Pulizia. Non lasciare codice inutile nella codebase (vecchio e non più utilizzato o creato "per ogni evenienza"). Riduci le duplicazioni e crea semplici astrazioni nella fase iniziale. Standardizzazione. Quando scrivi il codice, dovresti seguire lo stile e le pratiche stabilite per il repository. Autodisciplina. Man mano che le tecnologie usate si sviluppano e ne appaiono di nuove, gli sviluppatori spesso desiderano cambiare e migliorare qualcosa nel codice esistente. Non cedere troppo in fretta all'hype: studia attentamente i nuovi stack e solo per uno scopo specifico. Mantenere pulita la base di codice significa molto più che essere educati con i tuoi colleghi attuali e futuri. È essenziale per la sopravvivenza a lungo termine del programma. Più pulito è il tuo codice, più felici saranno gli sviluppatori, migliore sarà il prodotto e più a lungo durerà.

Perché Java è migliore di C++ per i sistemi a bassa latenza

Fonte: StackOverflow Come sviluppatori, sappiamo tutti che ci sono due modi per fare le cose: manualmente, lentamente e in modo fastidioso, o automaticamente, in modo difficile e veloce. Potrei usare l'intelligenza artificiale per scrivere questo articolo per me. Questo potrebbe farmi risparmiare molto tempo: l’intelligenza artificiale può generare migliaia di articoli al secondo, ma il mio editore probabilmente non sarebbe felice di apprendere che ci vorrebbero due anni per generare il primo articolo. Pausa caffè #64.  Come scrivere codice pulito.  Perché Java è migliore di C++ per i sistemi a bassa latenza - 2Una situazione simile si verifica quando si sviluppano sistemi software a bassa latenza. L'opinione comune è che sarebbe folle usare qualcosa di diverso dal C++ perché tutto il resto ha troppa latenza. Ma sono qui per convincerti del concetto opposto, controintuitivo, quasi eretico: quando si tratta di ottenere una bassa latenza nei sistemi software, Java è migliore. In questo articolo voglio prendere un esempio specifico di software che valorizza la bassa latenza: i trading system. Tuttavia, le argomentazioni qui presentate possono essere applicate a quasi tutte le circostanze in cui è richiesta o desiderata una bassa latenza. È semplicemente più facile discutere in relazione all'area di sviluppo in cui ho esperienza. E la verità è che la latenza è difficile da misurare. Tutto dipende da cosa intendi per bassa latenza. Scopriamolo adesso.

Saggezza acquisita

Poiché il C++ è molto più vicino all'hardware, la maggior parte degli sviluppatori ti dirà che la codifica in questo linguaggio offre un vantaggio in termini di velocità. In situazioni di bassa latenza, come il trading ad alta velocità, dove i millisecondi possono fare la differenza tra un pezzo di software utilizzabile e uno spreco di spazio su disco, C++ è considerato il gold standard. Almeno così era una volta. Ma la realtà è che molte grandi banche e broker ora utilizzano sistemi scritti in Java. E intendo scritto nativamente in Java, non scritto in Java e quindi interpretato in C++ per ridurre la latenza. Questi sistemi stanno diventando standard anche per le banche di investimento di primo livello, nonostante siano (presumibilmente) più lenti. Allora cosa sta succedendo? Sì, il C++ potrebbe avere una "bassa latenza" quando si tratta di eseguire il codice, ma sicuramente non è una bassa latenza quando si tratta di distribuire nuove funzionalità o anche di trovare sviluppatori che possano scriverlo.

Differenze (reali) tra Java e C++

Il problema dei tempi di sviluppo è solo l'inizio quando si tratta delle differenze tra Java e C++ nei sistemi del mondo reale. Per comprendere il vero valore di ciascuna lingua in questo contesto, scaviamo un po' più a fondo. Innanzitutto è importante ricordare il vero motivo per cui il C++ è più veloce di Java nella maggior parte dei casi: un puntatore C++ è l'indirizzo di una variabile in memoria. Ciò significa che il software può accedere direttamente alle singole variabili e non deve strisciare attraverso tabelle ad alta intensità di calcolo per cercarle. O almeno possono essere risolti specificando dove si trovano, perché con C++ spesso è necessario gestire in modo esplicito la durata e la proprietà degli oggetti. Di conseguenza, a meno che tu non sia veramente bravo a scrivere codice (un'abilità che può richiedere decenni per essere padroneggiata), il C++ richiederà ore (o settimane) di debug. E come ti dirà chiunque abbia provato a eseguire il debug di un motore Monte Carlo o di uno strumento di test PDE, provare a eseguire il debug dell'accesso alla memoria a un livello fondamentale può richiedere molto tempo. Un solo puntatore difettoso può facilmente far crollare un intero sistema, quindi rilasciare una nuova versione scritta in C++ può essere davvero terrificante. Naturalmente, non è tutto. Chi ama programmare in C++ farà notare che il garbage collector di Java soffre di picchi di latenza non lineari. Ciò è particolarmente vero quando si lavora con sistemi legacy, quindi l'invio di aggiornamenti al codice Java senza danneggiare i sistemi client può renderli così lenti da renderli inutilizzabili. In risposta, vorrei sottolineare che negli ultimi dieci anni è stato fatto molto lavoro per ridurre la latenza creata da Java GC. Disgregatore LMAX, ad esempio, è una piattaforma di trading a bassa latenza scritta in Java, anch'essa costruita come un framework che ha "interazione meccanica" con l'hardware su cui gira e non richiede blocco. I problemi possono essere ulteriormente mitigati se si crea un sistema che utilizza un processo di integrazione e distribuzione continua (CI/CD), poiché CI/CD consente la distribuzione automatizzata delle modifiche al codice testate. Questo perché CI/CD fornisce un approccio iterativo per ridurre la latenza della garbage collection, in cui Java può migliorare e adattarsi in modo incrementale a specifici ambienti hardware senza il processo dispendioso in termini di risorse di preparazione del codice per diverse specifiche hardware prima della spedizione. Poiché il supporto Java dell'IDE è molto più ampio di C++, la maggior parte dei framework (Eclipse, IntelliJ IDEA) consente di eseguire il refactoring di Java. Ciò significa che gli IDE possono ottimizzare il codice per prestazioni a bassa latenza, sebbene questa capacità sia ancora limitata quando si lavora con C++. Anche se il codice Java non raggiunge del tutto la velocità del C++, la maggior parte degli sviluppatori trova comunque più semplice ottenere prestazioni accettabili in Java che in C++.

Cosa intendiamo per "più veloce"?

In effetti, ci sono buone ragioni per dubitare che il C++ sia veramente "più veloce" o addirittura abbia una "latenza inferiore" rispetto a Java. Mi rendo conto che sto entrando in acque piuttosto torbide e che molti sviluppatori inizieranno a mettere in dubbio la mia sanità mentale. Ma ascoltami. Immaginiamo questa situazione: hai due sviluppatori, uno scrive in C++ e l'altro in Java, e chiedi loro di scrivere da zero una piattaforma di trading ad alta velocità. Di conseguenza, un sistema scritto in Java impiegherà più tempo per completare le transazioni commerciali rispetto a un sistema scritto in C++. Tuttavia, Java ha molti meno casi di comportamento indefinito rispetto a C++. Per fare solo un esempio, l'indicizzazione all'esterno di un array è un bug sia in Java che in C++. Se lo fai accidentalmente in C++, potresti ottenere un segfault o (più spesso) ti ritroverai con un numero casuale. In Java, uscire dai limiti genera sempre un errore ArrayIndexOutOfBoundsException . Ciò significa che il debug in Java è molto più semplice perché gli errori vengono generalmente identificati immediatamente e la posizione dell'errore è più facile da rintracciare. Inoltre, almeno secondo la mia esperienza, Java è più bravo a riconoscere quali parti di codice non devono essere eseguite e quali sono fondamentali per il funzionamento del software. Ovviamente puoi passare giorni a modificare il tuo codice C++ in modo che non contenga assolutamente codice estraneo, ma nel mondo reale ogni pezzo di software contiene un po' di volume e Java è più bravo a riconoscerlo automaticamente. Ciò significa che nel mondo reale Java è spesso più veloce di C++, anche secondo i parametri di latenza standard. E anche laddove ciò non accade, la differenza di latenza tra le lingue è spesso sopraffatta da altri fattori che non sono abbastanza grandi da avere importanza anche nel commercio ad alta velocità.

Vantaggi di Java per sistemi a bassa latenza

Tutti questi fattori, a mio avviso, costituiscono un argomento piuttosto convincente a favore dell'utilizzo di Java per scrivere piattaforme di trading ad alta velocità (e sistemi a bassa latenza in generale, ne parleremo tra poco). Tuttavia, per influenzare un po' gli appassionati di C++, diamo un'occhiata ad alcuni ulteriori motivi per utilizzare Java:
  • Innanzitutto, qualsiasi latenza in eccesso introdotta da Java nel tuo software sarà probabilmente molto inferiore rispetto ad altri fattori che influiscono sulla latenza, come i problemi di Internet. Ciò significa che qualsiasi codice Java (ben scritto) può facilmente funzionare altrettanto bene del C++ nella maggior parte delle situazioni di trading.

  • Il tempo di sviluppo più breve di Java significa anche che, nel mondo reale, il software scritto in Java può adattarsi ai cambiamenti hardware (o anche alle nuove strategie commerciali) più rapidamente rispetto al C++.

  • Se approfondisci questo argomento, vedrai che anche l'ottimizzazione del software Java può essere più veloce (se considerata nell'intero software) rispetto a un'attività simile in C++.

In altre parole, puoi benissimo scrivere codice Java per ridurre la latenza. Devi solo scriverlo come C++, tenendo presente la gestione della memoria in ogni fase di sviluppo. Il vantaggio di non scrivere in C++ è che il debug, lo sviluppo agile e l'adattamento a più ambienti sono più semplici e veloci in Java.

conclusioni

A meno che tu non stia sviluppando sistemi di trading a bassa latenza, probabilmente ti starai chiedendo se quanto sopra si applica al tuo caso. La risposta, salvo pochissime eccezioni, è sì. Il dibattito su come raggiungere una bassa latenza non è né nuovo né esclusivo del mondo della finanza. Per questo motivo si possono trarre preziosi insegnamenti per altre situazioni. In particolare, l'argomentazione di cui sopra secondo cui Java è "migliore" perché è più flessibile, più tollerante ai guasti e, in definitiva, più veloce da sviluppare e mantenere, può essere applicata a molte aree dello sviluppo software. Le ragioni per cui (personalmente) preferisco scrivere sistemi a bassa latenza in Java sono le stesse ragioni che hanno reso il linguaggio così vincente negli ultimi 25 anni. Java è facile da scrivere, compilare, eseguire il debug e apprendere. Ciò significa che puoi dedicare meno tempo alla scrittura del codice e più tempo all'ottimizzazione. In pratica, ciò porta a sistemi di scambio più affidabili e più veloci. E questo è tutto ciò che conta per il trading ad alta velocità.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION