Miten projektia ei saa tappaa huonoilla koodauskäytännöillä?
Bartosz Slysz
Software Engineer
Monet uraansa aloittavat ohjelmoijat pitävät muuttujien, funktioiden, tiedostojen ja muiden osien nimeämistä ei kovin tärkeänä. Tämän seurauksena heidän suunnittelulogiikkansa on usein oikea - algoritmit toimivat nopeasti ja tuottavat halutun vaikutuksen, mutta voivat olla tuskin luettavissa. Tässä artikkelissa yritän lyhyesti kuvata, mitä meidän pitäisi ottaa huomioon, kun nimeämme eri koodielementtejä, ja miten emme pääse ääripäästä toiseen.
Miksi nimeämisvaiheen laiminlyönti pidentää (joissakin tapauksissa - valtavasti) projektisi kehitystä?
Oletetaan, että sinä ja joukkue ottavat haltuunsa koodi muilta ohjelmoijilta. Osoite projekti you inherit kehitettiin ilman minkäänlaista rakkautta - se toimi hienosti, mutta sen jokainen elementti olisi voitu kirjoittaa paljon paremmin.
Kun on kyse arkkitehtuurista, koodin periytyminen herättää lähes aina vihaa ja vihaa ohjelmoijissa, jotka ovat saaneet sen. Joskus syynä on kuolevien (tai sukupuuttoon kuolleiden) teknologioiden käyttö, joskus väärä tapa ajatella sovellusta kehityksen alussa ja joskus yksinkertaisesti vastuussa olevan ohjelmoijan tiedon puute.
Joka tapauksessa projektin edetessä on mahdollista päästä pisteeseen, jossa ohjelmoijat ovat raivoissaan arkkitehtuurien ja tekniikoiden suhteen. Loppujen lopuksi jokainen sovellus tarvitsee jonkin ajan kuluttua joidenkin osien uudelleenkirjoittamista tai vain muutoksia tiettyihin osiin - se on luonnollista. Mutta ongelma, joka saa ohjelmoijat harmaantumaan, on vaikeus lukea ja ymmärtää perimäänsä koodia.
Varsinkin ääritapauksissa, kun muuttujat nimetään yksittäisillä, merkityksettömillä kirjaimilla ja funktiot ovat äkillinen luovuuden aalto, joka ei ole millään tavalla johdonmukainen muun sovelluksen kanssa, ohjelmoijasi saattavat sekoilla. Tällaisessa tapauksessa mikä tahansa koodianalyysi, joka voitaisiin suorittaa nopeasti ja tehokkaasti oikealla nimeämisellä, vaatii lisäanalyysiä esimerkiksi funktiotuloksen tuottamisesta vastaavista algoritmeista. Ja tällainen analyysi, vaikkakin huomaamaton - hukkaa valtavasti aikaa.
Uusien toiminnallisuuksien toteuttaminen sovelluksen eri osissa tarkoittaa painajaismaista analysointia, ja jonkin ajan kuluttua sinun on palattava koodiin ja analysoitava se uudelleen, koska sen tarkoitusperät eivät ole selvillä ja koska aiempi aika, jonka olet käyttänyt sen toiminnan ymmärtämiseen, on mennyt hukkaan, koska et enää muista, mikä sen tarkoitus oli.
Ja näin meidät imaisee mukaansa epäjärjestyksen tornado, joka hallitsee sovellusta ja syö hitaasti jokaisen sen kehittämiseen osallistujan. Ohjelmoijat vihaavat hanketta, projektipäälliköt vihaavat selittää, miksi sen kehitysaika alkaa jatkuvasti pidentyä, ja asiakas menettää luottamuksensa ja suuttuu, koska mikään ei mene suunnitelmien mukaan.
Miten välttää sitä?
Totta puhuen - joitakin asioita ei voi jättää väliin. Jos olemme valinneet tiettyjä tekniikoita projektin alussa, meidän on oltava tietoisia siitä, että ajan mittaan niiden tuki joko lakkaa tai yhä harvemmat ohjelmoijat hallitsevat muutaman vuoden takaisia tekniikoita, jotka ovat hitaasti vanhentumassa. Jotkin kirjastot vaativat päivityksissään enemmän tai vähemmän monimutkaisia muutoksia koodiin, mikä aiheuttaa usein riippuvuuksien pyörteen, johon voi jäädä vielä enemmän jumiin.
Toisaalta tilanne ei ole niin musta, sillä tekniikat ovat tietysti vanhenemassa, mutta tekijä, joka varmasti hidastaa niitä sisältävien hankkeiden kehitysaikaa, on pitkälti ruma koodi. Tässä yhteydessä on tietysti mainittava Robert C. Martinin kirja - se on ohjelmoijien raamattu, jossa kirjailija esittelee paljon hyviä käytäntöjä ja periaatteita, joita tulisi noudattaa, jotta voidaan luoda täydellisyyteen pyrkivää koodia.
Perusasia muuttujien nimeämisessä on, että niiden tarkoitus ilmaistaan selkeästi ja yksinkertaisesti. Se kuulostaa melko yksinkertaiselta, mutta joskus monet ihmiset laiminlyövät sen tai jättävät sen huomiotta. Hyvä nimi määrittää, mitä muuttujan on tarkalleen ottaen tarkoitus tallentaa tai mitä funktion on tarkoitus tehdä - sitä ei voi nimetä liian yleisluonteisesti, mutta toisaalta siitä ei saa tulla pitkää litkua, jonka pelkkä lukeminen aiheuttaa aivoille melkoisen haasteen. Kun olemme jonkin aikaa työskennelleet laadukkaan koodin parissa, koemme immersioefektin, jossa pystymme alitajuisesti järjestämään nimeämisen ja tietojen välittämisen funktiolle siten, että kokonaisuus ei jätä harhakuvitelmia siitä, mikä tarkoitus sitä ohjaa ja mikä on sen kutsumisen odotettu tulos.
Toinen asia, joka löytyy JavaScripton muun muassa yritys ylioptimoida koodi, mikä tekee siitä monissa tapauksissa lukukelvotonta. On normaalia, että jotkin algoritmit vaativat erityistä huolellisuutta, mikä usein kuvastaa sitä, että koodin tarkoitus voi olla hieman monimutkaisempi. Kuitenkin tapaukset, joissa tarvitsemme liiallista optimointia, ovat äärimmäisen harvinaisia, tai ainakin ne, joissa koodimme on likaista. On tärkeää muistaa, että monet kieleen liittyvät optimoinnit tapahtuvat hieman alemmalla abstraktiotasolla; esimerkiksi V8-moottori voi riittävällä määrällä iteraatioita nopeuttaa silmukoita merkittävästi. On syytä korostaa sitä, että elämme 2000-luvulla emmekä kirjoita ohjelmia Apollo 13 -lentoa varten. Meillä on paljon enemmän liikkumavaraa resurssien suhteen - ne ovat olemassa käytettäväksi (mieluiten järkevällä tavalla :>).
Joskus koodin jakaminen osiin antaa todella paljon. Kun operaatiot muodostavat ketjun, jonka tarkoituksena on suorittaa tietystä tietojen muutoksesta vastuussa olevia toimia, on helppo eksyä. Siksi voit yksinkertaisella tavalla, sen sijaan että tekisit kaiken yhdessä ketjussa, pilkkoa tietystä asiasta vastaavat koodin osat yksittäisiin elementteihin. Näin yksittäisten operaatioiden tarkoitus ei ainoastaan selviä, vaan voit myös testata koodinpätkiä, jotka vastaavat vain yhdestä asiasta ja joita voidaan helposti käyttää uudelleen.
Käytännön esimerkkejä
Mielestäni tarkin esitys joistakin edellä esitetyistä lausunnoista on näyttää, miten ne toimivat käytännössä - tässä kappaleessa yritän hahmotella joitakin huonoja koodikäytäntöjä, jotka voidaan enemmän tai vähemmän muuttaa hyviksi. Osoitan, mikä häiritsee koodin luettavuutta joissakin hetkissä ja miten se voidaan estää.
Yksikirjaimisten muuttujien kirous
Kauhea käytäntö, joka on valitettavasti melko yleinen jopa yliopistoissa, on muuttujien nimeäminen yhdellä kirjaimella. On vaikea olla myöntämättä, että joskus se on varsin kätevä ratkaisu - vältämme tarpeettoman pohdinnan siitä, miten määritetään muuttujan tarkoitus, ja sen sijaan, että käyttäisimme useita tai useampia merkkejä muuttujan nimeämiseen, käytämme vain yhtä kirjainta - esim. i, j, k.
Paradoksaalista kyllä, joihinkin näiden muuttujien määritelmiin on liitetty paljon pidempi kommentti, josta selviää, mitä kirjoittaja on tarkoittanut.
Hyvä esimerkki tästä olisi esittää iteraatio kaksiulotteisen matriisin yli, joka sisältää vastaavat arvot sarakkeen ja rivin leikkauspisteessä.
const array = [[0, 1, 2], [3, 4, 5], [6, 7, 8]];
// melko huono
for (let i = 0; i < array[i]; i++) {
for (let j = 0; j < array[i][j]; j++) {
// tässä on sisältö, mutta aina kun i:tä ja j:tä käytetään, minun täytyy palata takaisin ja analysoida, mihin niitä käytetään.
}
}
// silti huono mutta hauska
let i; // rivi
let j; // sarake
for (i = 0; i < array[i]; i++) {
for (j = 0; j < array[i][j]; j++) {
// tässä on sisältö, mutta aina kun i:tä ja j:tä käytetään, minun on palattava takaisin ja tarkistettava kommentit, mihin niitä käytetään.
}
}
// paljon parempi
const rowCount = array.length;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { {
const row = array[rowIndex];
const columnCount = row.length;
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
const column = row[columnIndex];
// onko kenelläkään epäselvyyttä siitä, mikä on mitä?
}
}
Salakavala ylioptimointi
Eräänä kauniina päivänä törmäsin erittäin hienostuneeseen koodiin, jonka oli kirjoittanut eräs - ohjelmistosuunnittelija. Tämä insinööri oli keksinyt, että käyttäjän oikeuksien lähettämistä merkkijonoina, jotka määrittelevät tietyt toiminnot, voidaan optimoida huomattavasti käyttämällä muutamia bittitason temppuja.
Luultavasti tällainen ratkaisu olisi OK, jos kohde olisi Commodore 64, mutta tämän koodin tarkoitus oli yksinkertainen verkkosovellus, joka on kirjoitettu JS-kielellä. On tullut aika voittaa tämä omituisuus: Oletetaan, että käyttäjällä on koko järjestelmässä vain neljä vaihtoehtoa sisällön muokkaamiseen: luoda, lukea, päivittää ja poistaa. On melko luonnollista, että lähetämme nämä oikeudet joko JSON-muodossa tiloja sisältävän objektin avaimina tai joukkona.
Nokkela insinöörimme huomasi kuitenkin, että numero neljä on maaginen arvo binäärisessä esitystavassa, ja selvitti asian seuraavasti:
Koko kyvykkyystaulukossa on 16 riviä, mutta olen listannut vain neljä, jotta ymmärrät, miten nämä oikeudet luodaan. Oikeuksien lukeminen menee seuraavasti:
Mitä näet yllä ei ole WebAssembly-koodi. En halua, että minut ymmärretään väärin - tällaiset optimoinnit ovat normaali asia järjestelmissä, joissa tiettyjen asioiden täytyy viedä hyvin vähän aikaa tai muistia (tai molempia). Web-sovellukset eivät kuitenkaan todellakaan ole paikka, jossa tällaiset ylioptimoinnit ovat täysin järkeviä. En halua yleistää, mutta front-end-kehittäjien työssä suoritetaan harvoin monimutkaisempia, bittiabstraktiotasolle yltäviä operaatioita.
Se ei yksinkertaisesti ole luettavissa, ja ohjelmoija, joka osaa tehdä analyysin tällaisesta koodista, ihmettelee varmasti, mitä näkymättömiä etuja tällä ratkaisulla on ja mitä voi vahingoittua, kun kehitystiimi haluaa kirjoittaa sen uudelleen järkevämpään ratkaisuun.
Epäilen lisäksi, että lähettämällä käyttöoikeudet tavallisena objektina ohjelmoija voisi lukea aikomuksen 1-2 sekunnissa, kun taas koko asian analysointi alusta alkaen vie vähintään muutaman minuutin. Projektissa on useita ohjelmoijia, ja jokaisen heistä on törmättävä tähän koodinpätkään - heidän on analysoitava se useaan kertaan, koska jonkin ajan kuluttua he unohtavat, mitä ihmettä siellä tapahtuu. Kannattaako ne muutamat tavut säästää? Mielestäni ei.
Jaa ja hallitse
Verkkokehitys kasvaa nopeasti, eikä ole mitään merkkejä siitä, että mikään muuttuisi lähiaikoina tässä suhteessa. On myönnettävä, että front-end-kehittäjien vastuu on viime aikoina kasvanut merkittävästi - he ovat ottaneet vastuulleen logiikan osan, joka vastaa tietojen esittämisestä käyttöliittymässä.
Joskus tämä logiikka on yksinkertaista, ja API:n tarjoamilla objekteilla on yksinkertainen ja helppolukuinen rakenne. Joskus ne kuitenkin vaativat erityyppisiä kartoitus-, lajittelu- ja muita operaatioita, jotta ne voidaan sovittaa sivun eri kohtiin. Ja tämä on se kohta, jossa voimme helposti pudota suohon.
Olen monta kertaa saanut itseni kiinni siitä, että olen tehnyt suorittamani operaation tiedot lähes lukukelvottomiksi. Huolimatta array-metodien oikeasta käytöstä ja muuttujien oikeasta nimeämisestä, operaatioketjut menettivät joissakin kohdissa melkein kontekstin siitä, mitä halusin saavuttaa. Lisäksi joitakin näistä operaatioista piti joskus käyttää muualla, ja joskus ne olivat globaaleja tai tarpeeksi monimutkaisia, jotta ne vaativat testien kirjoittamista.
Tiedän, tiedän - tämä ei ole mikään triviaali koodinpätkä, joka havainnollistaa helposti sen, mitä haluan välittää. Ja tiedän myös, että näiden kahden esimerkin laskennallinen monimutkaisuus on hieman erilainen, vaikka 99% tapauksista meidän ei tarvitse huolehtia siitä. Algoritmien välinen ero on yksinkertainen, sillä molemmat laativat kartan sijainneista ja laitteiden omistajista.
Ensimmäinen valmistelee tämän kartan kahdesti, kun taas toinen valmistelee sen vain kerran. Ja yksinkertaisin esimerkki, joka osoittaa, että toinen algoritmi on helpommin siirrettävissä, on se, että meidän on muutettava tämän kartan luomisen logiikkaa ensimmäiseen ja esimerkiksi suljettava pois tietyt sijainnit tai muita outoja asioita, joita kutsutaan liiketoimintalogiikaksi. Toisessa algoritmissa muutamme vain kartan hankkimistapaa, kun taas kaikki muut myöhemmillä riveillä tapahtuvat tietomuutokset pysyvät ennallaan. Ensimmäisen algoritmin tapauksessa meidän on muokattava jokaista kartan valmisteluyritystä.
Ja tämä on vain esimerkki - käytännössä tällaisia tapauksia on paljon, kun meidän on muutettava tai uudistettava tiettyä tietomallia koko sovelluksen osalta.
Paras tapa välttää erilaisten liiketoimintamuutosten seuraaminen on laatia yleisiä työkaluja, joiden avulla voimme poimia kiinnostavat tiedot melko yleisellä tavalla. Jopa niiden 2-3 millisekunnin kustannuksella, jotka saatamme menettää optimoinnin vähentämisen vuoksi.
Yhteenveto
Ohjelmoijan ammatti on kuin mikä tahansa ammatti - opimme joka päivä uusia asioita ja teemme usein paljon virheitä. Tärkeintä on oppia näistä virheistä, kehittyä ammatissa paremmaksi ja olla toistamatta näitä virheitä tulevaisuudessa. Ei voi uskoa myyttiin, että tekemämme työ olisi aina virheetöntä. Voit kuitenkin muiden kokemusten perusteella vähentää virheitä vastaavasti.
Toivon, että tämän artikkelin lukeminen auttaa sinua välttämään ainakin joitakin niistä huonot koodauskäytännöt joita olen kokenut työssäni. Jos sinulla on kysyttävää parhaista koodikäytännöistä, voit tavoittaa minut osoitteesta Codestin miehistö ulos kuulemaan epäilyksiäsi.