Topp 10 Lettlandsbaserade mjukvaruutvecklingsföretag
Läs mer om Lettlands främsta mjukvaruutvecklingsföretag och deras innovativa lösningar i vår senaste artikel. Upptäck hur dessa teknikledare kan hjälpa till att lyfta ditt företag.
Många programmerare som är i början av sin karriär anser att det inte är särskilt viktigt att namnge variabler, funktioner, filer och andra komponenter. Som ett resultat är deras designlogik ofta korrekt - algoritmer körs snabbt och ger önskad effekt, samtidigt som de knappt kan läsas. I den här artikeln kommer jag kortfattat att försöka beskriva vad vi bör vägledas av när vi namnger olika kodelement och hur vi inte går från en ytterlighet till en annan.
Låt oss anta att du och din Team tar över koden från andra programmerare. De projekt you inherit utvecklades utan någon som helst kärlek - det fungerade alldeles utmärkt, men varje enskild del kunde ha skrivits på ett mycket bättre sätt.
När det gäller arkitekturen, i fallet med kodarv, utlöser det nästan alltid hat och ilska från de programmerare som fick det. Ibland beror detta på användning av döende (eller utdöda) tekniker, ibland på fel sätt att tänka på applikationen i början av utvecklingen, och ibland helt enkelt på grund av bristande kunskap hos den ansvariga programmeraren.
I vilket fall som helst, när projekttiden går, är det möjligt att nå en punkt där programmerare är rasande galna på arkitekturer och tekniker. När allt kommer omkring behöver varje applikation omskrivning av vissa delar eller bara ändringar i specifika delar efter en tid - det är naturligt. Men det problem som kommer att göra programmerarnas hår grått är svårigheten att läsa och förstå den kod de ärvt.
Särskilt i extrema fall när variabler namnges med enstaka, meningslösa bokstäver och funktioner är en plötslig våg av kreativitet, som inte på något sätt överensstämmer med resten av applikationen, kan dina programmerare gå bärsärkagång. I ett sådant fall kräver varje kodanalys som skulle kunna utföras snabbt och effektivt med korrekt namngivning ytterligare analys av de algoritmer som är ansvariga för att producera funktionsresultatet, till exempel. Och en sådan analys, även om den är oansenlig - slösar bort en enorm mängd tid.
Att implementera nya funktioner i olika delar av applikationen innebär att man går igenom mardrömmen att analysera den, efter en tid måste man gå tillbaka till koden och analysera den igen eftersom dess avsikter inte är tydliga, och den tidigare tid som spenderades på att försöka förstå dess funktion var bortkastad eftersom man inte längre kommer ihåg vad syftet var.
Och så sugs vi in i en tornado av oordning som regerar i applikationen och långsamt konsumerar alla deltagare i dess utveckling. Programmerare hatar projektet, projektledare hatar att förklara varför utvecklingstiden börjar öka konstant, och kunden tappar förtroendet och blir arg för att ingenting går enligt plan.
Låt oss inse det - vissa saker går inte att hoppa över. Om vi har valt vissa tekniker i början av projektet måste vi vara medvetna om att de med tiden antingen kommer att sluta stödjas eller att allt färre programmerare kommer att behärska tekniker från några år sedan som långsamt håller på att bli föråldrade. Vissa bibliotek kräver i sina uppdateringar mer eller mindre omfattande ändringar i koden, vilket ofta medför en virvel av beroenden där man kan fastna ännu mer.
Å andra sidan är det inte ett så svart scenario; naturligtvis - teknologierna blir äldre, men den faktor som definitivt saktar ner utvecklingstiden för projekt som involverar dem är till stor del ful kod. Och naturligtvis måste vi här nämna boken av Robert C. Martin - det här är en bibel för programmerare, där författaren presenterar många goda metoder och principer som bör följas för att skapa kod som strävar efter perfektion.
Jag tror att den mest korrekta representationen av några av påståendena ovan är att visa hur de fungerar i praktiken - i det här stycket ska jag försöka beskriva några dåliga kodmetoder som mer eller mindre kan omvandlas till bra. Jag kommer att peka på vad som stör läsbarheten av koden i vissa ögonblick och hur man kan förhindra det.
En hemsk metod som tyvärr är ganska vanlig, till och med på universitet, är att namnge variabler med en enda bokstav. Det är svårt att inte hålla med om att det ibland är en ganska bekväm lösning - vi slipper onödigt funderande på hur vi ska avgöra syftet med en variabel och istället för att använda flera eller flera tecken för att namnge den använder vi bara en bokstav - t.ex. i, j, k.
Paradoxalt nog är vissa definitioner av dessa variabler försedda med en mycket längre kommentar, som avgör vad författaren hade i åtanke.
Ett bra exempel här skulle vara att representera iterationen över en tvådimensionell matris som innehåller motsvarande värden i skärningspunkten mellan kolumn och rad.
const array = [[0, 1, 2], [3, 4, 5], [6, 7, 8]];
// ganska dåligt
for (let i = 0; i < array[i]; i++) {
for (let j = 0; j < array[i][j]; j++) {
// här är innehållet, men varje gång i och j används måste jag gå tillbaka och analysera vad de används till
}
}
// fortfarande dåligt men roligt
låt i; // rad
låt j; // kolumn
for (i = 0; i < array[i]; i++) {
for (j = 0; j < array[i][j]; j++) {
// här är innehållet, men varje gång i och j används måste jag gå tillbaka och kolla kommentarer vad de används till
}
}
// mycket bättre
const rowCount = array.length;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const rad = array[radIndex];
const columnCount = rad.längd;
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
const column = rad[columnIndex];
// är det någon som tvivlar på vad som är vad?
}
}
En vacker dag stötte jag på en mycket sofistikerad kod skriven av en Programvaruingenjör. Den här ingenjören hade kommit på att det gick att optimera sändningen av användarrättigheter som strängar med specifika åtgärder med hjälp av några trick på bitnivå.
Förmodligen skulle en sådan lösning vara OK om målet var Commodore 64, men syftet med den här koden var en enkel webbapplikation, skriven i JS. Tiden har kommit för att övervinna denna egenhet:
Låt oss säga att en användare bara har fyra alternativ i hela systemet för att ändra innehåll: skapa, läsa, uppdatera, radera. Det är ganska naturligt att vi antingen skickar dessa behörigheter i JSON-form som nycklar till ett objekt med tillstånd eller som en array.
Men vår smarta ingenjör upptäckte att siffran fyra är ett magiskt värde i den binära presentationen och räknade ut det på följande sätt:
Hela tabellen med funktioner har 16 rader, jag har bara listat 4 för att förmedla idén om att skapa dessa behörigheter. Läsningen av behörigheterna går till på följande sätt:
const user = { behörigheter: 11 };
const canCreate = Boolean(user.permissions & 8); // true
const canRead = Boolean(användare.behörigheter & 4); // false
const canUpdate = Boolean(användare.behörigheter & 2); // true
const canDelete = Boolean(användare.behörigheter & 1); // true
Det du ser ovan är inte WebAssembly-kod. Jag vill inte bli missförstådd här - sådana optimeringar är en normal sak för system där vissa saker måste ta mycket lite tid eller minne (eller båda). Webbapplikationer, å andra sidan, är definitivt inte en plats där sådana överoptimeringar är helt meningsfulla. Jag vill inte generalisera, men i arbetet med frontend-utvecklare utförs sällan mer komplexa operationer som når nivån för bitabstraktion.
Den är helt enkelt inte läsbar, och en programmerare som kan göra en analys av en sådan kod kommer säkert att undra vilka osynliga fördelar den här lösningen har och vad som kan skadas när utvecklingsteamet vill skriva om den till en mer rimlig lösning.
Dessutom - jag misstänker att om man skickar behörigheterna som ett vanligt objekt skulle en programmerare kunna läsa avsikten på 1-2 sekunder, medan det tar minst några minuter att analysera hela den här saken från början. Det kommer att finnas flera programmerare i projektet, var och en av dem kommer att behöva stöta på den här koden - de kommer att behöva analysera den flera gånger, för efter en tid kommer de att glömma vilken magi som händer där. Är det värt att spara dessa få byte? Enligt min åsikt, nej.
Webbutvecklingen växer snabbt och det finns inget som tyder på att något snart kommer att förändras i detta avseende. Vi måste erkänna att ansvaret för frontend-utvecklare nyligen har ökat avsevärt - de tog över den del av logiken som ansvarar för presentationen av data i användargränssnittet.
Ibland är denna logik enkel och de objekt som tillhandahålls av API:et har en enkel och läsbar struktur. Ibland kräver de dock olika typer av mappning, sortering och andra operationer för att anpassa dem till olika platser på sidan. Och det är här vi lätt kan falla ner i träsket.
Många gånger har jag kommit på mig själv med att göra data i de operationer jag utförde praktiskt taget oläsliga. Trots korrekt användning av array-metoder och korrekt namngivning av variabler, förlorade kedjorna av operationer vid vissa tillfällen nästan sammanhanget för vad jag ville uppnå. Dessutom behövde vissa av dessa operationer ibland användas på andra ställen, och ibland var de globala eller tillräckligt sofistikerade för att kräva att man skrev tester.
const enheter = [
{ id: "001", version: 1, ägare: { plats: "Texas", ålder: 21 } },
{ id: '002', version: 2, ägare: { plats: 'Texas', ålder: 27 } },
{ id: "003", version: 3, ägare: { plats: "Arizona", ålder: 27 } } },
{ id: '004', version: 2, ägare: { plats: 'Chicago', ålder: 24 } } },
{ id: "005", version: 2, ägare: { plats: "Arizona", ålder: 19 } } },
{ id: "006", version: 3, ägare: { plats: "Texas", ålder: 42 } } },
];
// ganska komplicerat
(() => {
const locationAgeMap = enheter
.map(enhet => enhet.ägare)
.reducera((karta, objekt) => {
if (!map[objekt.plats]) {
map[objekt.plats] = objekt.ålder;
} annars {
map[objektets.plats] += objektets.ålder;
}
returnera map;
}, {});
const locationLegalAgeOwnersMap = enheter
.map(enhet => enhet.ägare)
.filter(ägare => ägare.ålder >= 21)
.reduce((karta, objekt) => {
if (!map[objekt.plats]) {
map[objekt.plats] = [objekt];
} annars {
map[item.location].push(item);
}
return map;
}, {});
console.log({ locationAgeMap, locationLegalAgeOwnersMap });
})();
// fortfarande komplicerat men mer kompakt
(() => {
const locationOwnersMap = enheter
.map(enhet => enhet.ägare)
.reducera((karta, objekt) => {
if (!map[objekt.plats]) {
map[objekt.plats] = [objekt];
} annars {
map[objekt.plats].push(objekt);
}
return map;
}, {});
const locationAgeMap = Object.fromEntries(
Object.entries(locationOwnersMap).map(([plats, ägare]) => {
const ownersAge = owners.map(ägare => ägare.ålder).reduce((a, b) => a + b);
return [plats, ägareÅlder];
})
);
const locationLegalAgeOwnersMap = Object.fromEntries(
Object.entries(locationOwnersMap).map(([location, ägare]) => {
const filteredOwners = ägare.filter(ägare => ägare.ålder >= 21);
return [plats, filtrerade ägare];
}).filter(([_plats, ägare]) => {
returnerar ägare.längd > 0;
})
);
console.log({ locationAgeMap, locationLegalAgeOwnersMap });
})();
Jag vet, jag vet - det här är inte någon trivial kodbit som enkelt illustrerar vad jag vill förmedla. Och jag vet också att beräkningskomplexiteten i de två exemplen är något annorlunda, medan vi i 99% av fallen inte behöver oroa oss för det. Skillnaden mellan algoritmerna är enkel eftersom de båda förbereder en karta över platser och enhetsägare.
Den första skapar denna karta två gånger, medan den andra bara gör det en gång. Och det enklaste exemplet som visar oss att den andra algoritmen är mer portabel ligger i det faktum att vi måste ändra logiken för att skapa denna karta för den första och t.ex. göra uteslutning av vissa platser eller andra konstiga saker som kallas affärslogik. När det gäller den andra algoritmen ändrar vi bara sättet att hämta kartan, medan alla övriga datamodifieringar som sker i de efterföljande raderna förblir oförändrade. När det gäller den första algoritmen måste vi justera varje försök att förbereda kartan.
Och det här är bara ett exempel - i praktiken finns det många sådana fall när vi behöver omvandla eller omarbeta en viss datamodell runt hela applikationen.
Det bästa sättet att undvika att hålla jämna steg med olika affärsförändringar är att förbereda globala verktyg som gör det möjligt för oss att extrahera information av intresse på ett ganska generiskt sätt. Även på bekostnad av de 2-3 millisekunder som vi kanske förlorar på grund av avoptimering.
Att vara programmerare är ett yrke som alla andra - varje dag lär vi oss nya saker och gör ofta en hel del misstag. Det viktigaste är att lära sig av dessa misstag, bli bättre i sitt yrke och inte upprepa dessa fel i framtiden. Man kan inte tro på myten att det arbete vi utför alltid kommer att vara felfritt. Du kan dock, baserat på andras erfarenheter, minska bristerna i enlighet med detta.
Jag hoppas att du genom att läsa den här artikeln kan undvika åtminstone några av de dålig kodningspraxis som jag har upplevt i mitt arbete. Om du har några frågor om bästa kodpraxis kan du nå Codest-besättningen ut för att rådgöra med dina tvivel.
Läs mer om detta:
Strategier för datahämtning i NextJS
Säkerhet i webbapplikationer - XSS-sårbarhet
Säkerhet i webbapplikationer. Sårbarhet med mål="_blank"
*Titelgrafiken kommer från speednet.pl