Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
C++ och dess framtid att programmera minnessäkert - Hur går utvecklingen?
Sedan har jag lång erfarenhet, som de flesta så lär man sig, ingen konstighet med det. För ett tag sedan försökte jag uppskatta hur mycket C++ kod jag skrivit i mitt liv. Det är minst 4 miljoner rader kod, förutom det har jag minst en halv miljon javascript kod. Kodat annat med men där är det mindre.
Har du något bra sätt att uppskatta detta? Har väldigt dålig koll på hur man skulle kunna uppskatta mängden kod som skrivit på jobbet under nästan 30 år.
Har länge sparat alla tester, experiment och privata projekt jag skrivit. Men hade inte kört en SLOC-beräkning på det. Visade sig vara mer kod än jag trodde där, ca 1,6 miljoner 550k rader (det i >20 olika språk men majoriteten är i de jag nämnt här, så C, C++, Go, Rust och Python, men finns R, Erlang, Haskell, Scala, Kotlin, Swift, Lua, m.m.)
Edit: 550k är fortfarande mer än jag trodde givet att det är sidogrejor. Var 1,6m blev uppenbart fel när jag såg fördelningen av språk, har skrivit relativt lite Python utöver att använda det i ett par IoT-projekt men det stod för lejonparten. Nu ska det stämma...
Om jag tar ett exempel. Om jag i C++ plussar en pointer, då vet kompilatorn storleken på objektet och vet hur långt pekaren skall flyttas, fungerar det så i rust med?
I C++ behöver jag inte massa metoder, jag kan gå direkt på data
reinterpret_cast använder jag sällan
Så vad du säger är: du saknar pekararitmetik i de övriga språken? Är det då det enda (för "you guessed it" kan visa hur man gör det i nämnda språk också om man absolut måste...).
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Har du något bra sätt att uppskatta detta? Har väldigt dålig koll på hur man skulle kunna uppskatta mängden kod som skrivit på jobbet under nästan 30 år.
Inget bra sätt nej. Men för mig och när jag varit med och lärt upp eller kanske blivit upplärd så har det alltid varit viktigt att skriva kod. Också viktigt att designa kod så nya utvecklare får skriva även om de kanske inte gör något vettigt i början.
Och för mig är också kodmängd en teknik för att se om arkitekturen är ok. Fungerar arkitekturen bra skall det gå lika snabbt att skriva kod oavsett hur stort projektet är. Om det är 1 000, 10 000 eller 100 000 000 rader kod skall inte spela någon roll.
Förutom det så använder jag hungarian notation
*exempel*
| Postfix | Description | Sample |
| ------------ | ----------- | ------ |
| `b`* | **boolean** | `bool bOk, bIsOk, bIsEof, bResult;` |
| `i`* | **signed integer** (all sizes) | `int iCount;` `int64_t iBigValue;` `int16_t iPosition; char iCharacter;` |
| `u`* | **unsigned integer** (all sizes) | `unsigned uCount;` `uint64_t uBigValue;` `uint8_t uCharacter;` `size_t uLength;` |
| `d`* | **decimal values** (double, float) | `double dSalary;` `float dXAxis;` `double dMaxValue;` |
| `p`* | **pointer** (all, including smart pointers) | `int* piNumber;` `int piNumber[20];` `void* pUnknown;` `std::unique_ptr<std::atomic<uint64_t>[]> pThreadResult;` |
| `e`* | **enum values** | `enum enumBodyType { eUnknown, eXml, eJson };` `enumBodyType eType = eJson;` |
| `it`* | **iterator** | `for( auto it : vectorValue ) {...}` `for( auto it = std::begin( m_vectorOption ), itEnd = std::end( m_vectorOption ); it != itEnd; it++ ) {...}` |
| `m_`* | **member variables** | `uint64_t m_uRowCount;` `std::vector<column> m_vectorColumn;` `uint8_t* m_puTableData = nullptr;` |
| `string`* | **all string objects** | `std::string_view stringName;` `std::string stringName;` `std::wstring stringName;` |
| *`_` | **view declaration** | `boost::beast::http::file_body::value_type body_;` |
Med hungarian blir utvecklare snabba
Produktion av kodrader har alltid varit något jag kontrollerat, kanske udda.
Självklart så en majoritet av alla rader kod man gjort, det är ju skitkod och slängts
Ja och de fokuserar på språket C++, de är språknördar och det skiljer mot utvecklare.
För utvecklare är endast C/C++ ett verktyg för att ta sig från A till B. Dyker det upp ett bättre verktyg så hade jag bytt på fem minuter.
Bjarne och Herb (och en hel del till) har fokus på språket vilket är något annat. De måste tänka på saker som jag som utvecklare inte bryr mig om
Jag skulle gärna se en lista på saker du vill ha i det perfekta språket och saker du vill att det inte ska ha. Jag har svårt att få ihop vad det är du tycker är viktigt i ett språk. Å ena sidan argumenterar du för konstruktorer som är starkt kopplade till RAII begreppet. Å andra sidan ratar du smart pointers som är gjorda för att förenkla RAII.
Så vad du säger är: du saknar pekararitmetik i de övriga språken? Är det då det enda (för "you guessed it" kan visa hur man gör det i nämnda språk också om man absolut måste...).
Jag har inte testat att skriva rust kod och kommer inte göra det har skrivit en del C# och för många år sedan en del java. De här språken fungerar inte alls för mig. Som tur är finns det gott om andra programmerare som delar mina åsikter och har därför kunnat arbeta med intresset.
Rust förstod jag inte, bara att rust utvecklare är så löjligt aktiva så svårt att undvika.
Nu vet jag mer om språket och jag tror faktiskt inte språket har en framtid. Eller så får de ta till en del ny funktionalitet. Problemet där är att språket försöker vara för mycket och designen är anpassad efter det.
Andra språk med GC försvinner direkt.
Det jag nyligen lärt mig om rust
Att inte kunna överlagra, och Drop och new logiken, ingen hitt i min bok och får därför också bättre förståelse för varför rust utvecklare så ofta förkortar namn för det är inget jag gillar. Förkortningar är enligt mig ett tecken på fel i arkitekturen.
Menar du då att du skriver kod med namn efter domänen (användaren)
Mitt tips till det är att alltid göra vad du kan för att lyfta bort domänen från koden. Den del där domänen måste in bör göras så tunn som möjligt. Så fort utvecklare måste ha domänkunskap för att hantera koden blir det en extra tröskel
Nej, jag kodar i Unix och Linux-miljö, just nu "bare metal x86" (utan operativsystem). Lite nybörjare just på Intel-assemblern, men tänket finns där redan sedan gammalt så snart sitter alla varianterna. Är mest irriterad på att tilldelning inte sätter vissa flaggor så man slipper compare. Det hade varit smidigt om "mov rax,0" hade satt ZF så att "jz" hade kunnat utföras direkt utan en "cmp" dessförinnan. Det hade sparat en cykel.
Jag skulle gärna se en lista på saker du vill ha i det perfekta språket och saker du vill att det inte ska ha.
Det går inte, finns inget språk som klarar allt. Datorer skiljer sig så starkt ifrån hur människor tänker så man måste stegvis koppla ihop funktionalitet för människor med datorer.
Jag har svårt att få ihop vad det är du tycker är viktigt i ett språk. Å ena sidan argumenterar du för konstruktorer som är starkt kopplade till RAII begreppet. Å andra sidan ratar du smart pointers som är gjorda för att förenkla RAII.
Smartpointers är "kladdigt".
Att C++ har konstruktor och destruerar räcker. Inte svårt att lägga in en delete i destructorn.
Och arkitektur är så mycket viktigare, slarv med minne tvingar utvecklare att tänka på arkitekturen. Du måste lära dig det
Jämför med GC språk, du kan göra som du vill och behöver därför inte lära dig
Det går inte, finns inget språk som klarar allt. Datorer skiljer sig så starkt ifrån hur människor tänker så man måste stegvis koppla ihop funktionalitet för människor med datorer.
Nu frågade jag inte om vilket språk som klarar allt du vill. Jag frågade efter de specifika features du vill se i ett hypotetiskt språk. Tolkar ditt svar som att du inte vet.
Smartpointers är "kladdigt".
Ja just det. Det vedertagna begreppet "kladdigt" som alla programmerare känner till. Det låter inte alls som något subjektivt... Nu gissar jag lite här att tankegången i ditt huvud efter att du läst det här blir "det går inte diskutera med programmerare för de förstår inte".
Att C++ har konstruktor och destruerar räcker. Inte svårt att lägga in en delete i destructorn.
Här är ett exempel där du läcker minne med nakna new/delete jämfört med smart pointers (genererat av ChatGPT). För att undvika detta måste du stänga av exceptions. Gör du det?
void raw_version() {
Resource* r = new Resource; // allocate
throw std::runtime_error("oops");
delete r; // never reached → leak
}
void smart_version() {
std::unique_ptr<Resource> r{ new Resource }; // allocate
throw std::runtime_error("oops");
// unique_ptr’s destructor runs automatically → cleaned up
}
Och arkitektur är så mycket viktigare, slarv med minne tvingar utvecklare att tänka på arkitekturen. Du måste lära dig det
Jämför med GC språk, du kan göra som du vill och behöver därför inte lära dig
Du verkar inte ha förstått det här än men Rust har ingen GC. C++ var närmare att ha en GC än Rust.
Jag skulle nog vilja påstå att det exemplet på "tydligare kod" inte hjälper läsbarheten, det blir bara för explicit och långrandigt. Ser inga problem med att korta ner variabelnamnen till:
vNumber och vEvenNumber
då definitionerna förklarar detaljerna och det bör inte ta mer än 15 sekunder för hjärnan att förstå vilka premisser koden i detta projekt är skriven. Jag skulle till och med stå ut med att använda pluraländelsen som implicit för en vektor.
Numbers och EvenNumbers
Men nu kanske jag närmar mig gränsen för vad som är tolererbart.
Från en som alltid döper iterationsvariablen till "i".
Är sökbar kod viktig för dig?
Låt säga att det skall vara viktigt och hitta det mesta med att söka i koden, använda ett regex uttryck
Om sökbarhet är viktigt, hur skriver du kod så den är lätt att hitta
Nu frågade jag inte om vilket språk som klarar allt du vill. Jag frågade efter de specifika features du vill se i ett hypotetiskt språk. Tolkar ditt svar som att du inte vet.
Det duger rätt bra det som finns. Man kan göra det mest i C till och med men det blir en hel del kod.
C++ har väldigt bra sätt att skriva smart kod, så var det redan på 1990 talet. Är man duktig på pekare och minnet kan du göra massor med lite. Backar du än mer gick det att skriva "självmodiferande kod", alltså kod som skrev om assemblerinstruktioner och det är otroligt hur mycket de fick in med lite kod om den tekniken används.
I C++ så det mesta nytt som kommer nu är för att trolla bort minneshantering och en del vill ha det, för mig är det mindre viktigt.
Förbättringarna i kompilatorer är viktigare och den utvecklingen har ju förbättrat för alla. Att kompilatorn vet så mycket mer och man kan ta hjälp av av den.
C++11 var den största uppdateringen och det beror framförallt på move konstruktorn. Annars är det mest syntaktiskt socker.
För C++ 26 ser jag fram emot reflection och att assert blir en del av språket. De två sakerna är riktigt stora.
Med reflection tror jag en hel ny typ av bibliotek bli vanliga eftersom det går att utöka funktionalitet.
Ja just det. Det vedertagna begreppet "kladdigt" som alla programmerare känner till. Det låter inte alls som något subjektivt... Nu gissar jag lite här att tankegången i ditt huvud efter att du läst det här blir "det går inte diskutera med programmerare för de förstår inte".
Så här:
Visar någon utvecklare mig kod så vill jag veta hur jag skall förstå koden utan att läsa koden. Jag vill att programmeraren förklarar för mig vilka principer som används, hur den designat så andra inte skall behöva gå in där.
Är det konstigt?
Låt säga att en utvecklare producerar i genomsnitt 100 rader kod per dag för att göra det enkelt och räkna. 2000 rader i månaden eller drygt 20 000 rader på ett år. Inte något extremt alls men skall man behöva läsa den koden för att förstå hur den fungerar, då går det inte. Trots relativt medioker hastighet kommer en enda utvecklare orsaka onödig tid för andra.
Om utvecklaren inte klarar förklara hur jag snabbt skall hitta saker och förstå hur jag arbetar med koden så visar jag gärna exempel på hur man kan göra. 200 rader per dag klarar de flesta med bra teknik.
För att undvika detta måste du stänga av exceptions. Gör du det?
Stänga av gör jag inte men exception är ett nybörjarfel. Aldrig använda det. Enda gången exception används är om det ligger i extern kod och man inte har något val eller om det skiter sig totalt, finns ingen återvändo. Kanske att minnet är slut
C++11 var den största uppdateringen och det beror framförallt på move konstruktorn. Annars är det mest syntaktiskt socker.
För C++ 26 ser jag fram emot reflection och att assert blir en del av språket. De två sakerna är riktigt stora.
Med reflection tror jag en hel ny typ av bibliotek bli vanliga eftersom det går att utöka funktionalitet.
Ja då kommer man kunna ha bibliotek som https://crates.io/crates/serde_json och https://crates.io/crates/clap. Men senaste jag hörde är att reflection inte kommer förrän C++29.
Så här:
Visar någon utvecklare mig kod så vill jag veta hur jag skall förstå koden utan att läsa koden. Jag vill att programmeraren förklarar för mig vilka principer som används, hur den designat så andra inte skall behöva gå in där.
Är det konstigt?
Nej det är inte konstigt. Men problemet som jag har redan nämnt är att du börjar med att säga att något är "kladdigt" som om andra bara ska förstå vad det begreppet betyder för dig. Sen så är det så här också att förståelse av kod är subjektivt. Bara för att du har svårt att läsa en viss typ av kod betyder det inte att vi andra har det. Om man håller sig till exemplet med smart pointers så tycker jag att det är lättare att läsa denna kod
class SmartOwner {
std::unique_ptr<Resource> res;
public:
SmartOwner()
: res(std::make_unique<Resource>()) {}
};
än denna
class RawOwner {
Resource* res;
public:
RawOwner()
: res(new Resource){}
~RawOwner() {
delete res;
}
};
(och återigen så skyddar koden med smart pointer från minnesläcka om konstruktorn kastar exception).
Stänga av gör jag inte men exception är ett nybörjarfel. Aldrig använda det. Enda gången exception används är om det ligger i extern kod och man inte har något val eller om det skiter sig totalt, finns ingen återvändo. Kanske att minnet är slut
Att ett annat bibliotek kastar exceptions kan du ju inte göra något åt. Använder du Valgrind eller liknande för att hitta minnesbuggarna som uppstår eller kör du på "trust me bro"?
Det duger rätt bra det som finns. Man kan göra det mest i C till och med men det blir en hel del kod.
C++ har väldigt bra sätt att skriva smart kod, så var det redan på 1990 talet. Är man duktig på pekare och minnet kan du göra massor med lite. Backar du än mer gick det att skriva "självmodiferande kod", alltså kod som skrev om assemblerinstruktioner och det är otroligt hur mycket de fick in med lite kod om den tekniken används.
Finns en stor anledning att inte använda självmodifierade kod ihop med moderna CPUer.
Bortsett från att det idag är totalt oportabelt (inte bara över CPU-arch utan även över OS) har moderna CPUer är en designade som gör det direkt olämpligt. Att frekvent ändra kodsegmentet är ett "bra" sätt att döda prestanda, även om man lyckas göra en korrekt implementation.
C++11 var den största uppdateringen och det beror framförallt på move konstruktorn. Annars är det mest syntaktiskt socker.
Kanske testa Rust? Där är move-semantics normalfallet för referenser, så lättare syntax jämfört med C++.
Men här måste jag fråga: kan du verkligen C++? För om det var den största förändring du såg i C++11 undrar jag lite vad du faktiskt skriver för C++.
Vad sägs om att C++11 är den första versionen som faktiskt definierar hur språket fungerar i program med fler än en tråd?
Eller vad sägs om constexpr?
Lambda-uttryck?
Auto (eller som Herb uttryckte det, "var" that spells "a-u-t-o")...
std::unordered_set/std::unordered_map?
std::tuple (även om det dröjde till C++17 innan det smidigt kunde användas för multipla returvärden, std::tie sög...)
Stänga av gör jag inte men exception är ett nybörjarfel. Aldrig använda det. Enda gången exception används är om det ligger i extern kod och man inte har något val eller om det skiter sig totalt, finns ingen återvändo. Kanske att minnet är slut
Tillhör de som anser att exceptions är "goto on steroids" och bör i stort sätt aldrig användas. Givet att exceptions trots allt finns i C++ gör att du faktiskt inte kan vara säker att ett sådan inte kastas om du inte har total koll på all kod du anropar.
Kanske testa Rust (eller Go)? Där finns inte exceptions
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Bara för att du har svårt att läsa en viss typ av kod betyder det inte att vi andra har det.
Absolut är det så och givetvis måste jag vara öppen för att det finns andra sätt och skriva kod även om man lärt sig en del under alla år. Att det finns motstånd i att skriva kod på olika sätt är givetvis något som alla utvecklare råkat på, även jag. Kan inte räkna antalet diskussioner man haft i ämnet.
Den lösningen jag försöker få till är att ge någon en svårare uppgift och be den lösa det. Klaras inte lösningen kan jag ge tips för oftast handlar det om teknik, inte skicklighet.
Saknar man "mönster" i hur kod skrivs så har alla en gräns, det går inte att minnas hur mycket som helst även om någon har riktigt hästminne. Klarar de själva inte att minnas eller trasslar till det, hur skall då andra klara det?
Om de testar en annan teknik, kanske med tips och klarar uppgiften. Då brukar det släppa men inte säkert. Att skriva kod som man "själv vill" är ibland svårt att släppa
Hungarian Notation som jag förordar är inte för att det är något jag gillar eller tycker är snyggt. Det är en objektiv observation över hur effektiv stilen är. Tror till och med att man lätt skulle kunna mäta hjärnans syreförbrukning och få svart på vitt.
Att ett annat bibliotek kastar exceptions kan du ju inte göra något åt. Använder du Valgrind eller liknande för att hitta minnesbuggarna som uppstår eller kör du på "trust me bro"?
Minnesläckor använder nog alla C++ utvecklare verktyg för, vi kör valgrind i molnet men lokalt så AddressSanitizer och LeakSanitizer på linux, de har bråkat en del i Windows så varit lite egenutvecklat där.
Men här måste jag fråga: kan du verkligen C++? För om det var den största förändring du såg i C++11 undrar jag lite vad du faktiskt skriver för C++.
Kan säga orsaken till varför det var så "stort" för mig.
STL gick inte och använda innan move kom eftersom det var för segt.
Har faktiskt utvecklat ett helt eget stl (med tankar) från det riktiga stl, i början på 2000 talet. Andra bibliotek hade alltid med sträng klasser med referensräknare istället för att kopiera och det fanns annat som vart problematiskt med stl.
Koden var för osmidig om man hade krav på prestanda
Efter C++11 gick det att använda stl och fördelen med det är att man själv inte behövde underhålla lika mycket kod
Kanske testa Rust?
om någon förklarar hur man kan skriva lika smidig kod som det går med språk där metoder kan överlagras så skall jag fundera på det igen.
Vad sägs om att C++11 är den första versionen som faktiskt definierar hur språket fungerar i program med fler än en tråd?
Eller vad sägs om constexpr?
Lambda-uttryck?
Auto (eller som Herb uttryckte det, "var" that spells "a-u-t-o")...
std::unordered_set/std::unordered_map?
std::tuple (även om det dröjde till C++17 innan det smidigt kunde användas för multipla returvärden, std::tie sög...)
Självklart är det funktionalitet som är bra, gör koden mer lättarbetad med det är mest godis
constexpr är super, auto används sparsamt i produktionskod, men smidigt i lek-kod
objekten i stl, säkert bra för många men för egen del är det mindre viktigt.
Den lösningen jag försöker få till är att ge någon en svårare uppgift och be den lösa det. Klaras inte lösningen kan jag ge tips för oftast handlar det om teknik, inte skicklighet.
Det här skulle jag gärna testa. Skulle säkert kunna lära mig mycket av det. Vad pratar vi om för slags problem?
Hungarian Notation som jag förordar är inte för att det är något jag gillar eller tycker är snyggt. Det är en objektiv observation över hur effektiv stilen är. Tror till och med att man lätt skulle kunna mäta hjärnans syreförbrukning och få svart på vitt.
Hungarian notation ser extremt fult ut men jag kan tänka mig att det har sina fördelar. Jag tycker att
m_
är väldigt bra även fast det också är fult. I Rust så skriver man alltid
self
innan member variabeln så det skulle inte göra någon skillnad där men i C++ blir det mycker mer lättläst.
Minnesläckor använder nog alla C++ utvecklare verktyg för, vi kör valgrind i molnet men lokalt så AddressSanitizer och LeakSanitizer på linux, de har bråkat en del i Windows så varit lite egenutvecklat där.
Bra det. Min personliga åsikt är att minne inte är så stort problem som många säger. Därmed inte sagt att jag vill skriva kod med råa pekare överallt, men jag tror att med rätt verktyg så kan man undvika de flesta problemen. Det ska dock sägas att det kräver arbete för att sätta upp dessa verktyg också och så ska man tolka resultaten och hitta lösningar. Rust är lite mer plug-and-play i det att kompilatorn helt enkelt inte kompilerar koden. Minnessäkerhet i Rust är för mig en bonus och inte främsta anledningen till att jag använder språket.
om någon förklarar hur man kan skriva lika smidig kod som det går med språk där metoder kan överlagras så skall jag fundera på det igen.
Macron klarar av att ta okänt antal argument lite liknande variadic macros i C. Som redan nämnts så kan du använda builder pattern också. Du har pratat mycket om lättläst kod. Tycker du att kod som överlagrar är lättläst? Jag tycker personligen att sån kod är svårare att läsa. Som jag nämnt några gånger så är Rust mer explicit och försöker vara lättare att läsa på bekostnad av mer verbos kod. Överlagring av funktioner kommer nog aldrig komma till Rust pga. det tankesättet.
Macron klarar av att ta okänt antal argument lite liknande variadic macros i C. Som redan nämnts så kan du använda builder pattern också. Du har pratat mycket om lättläst kod. Tycker du att kod som överlagrar är lättläst? Jag tycker personligen att sån kod är svårare att läsa. Som jag nämnt några gånger så är Rust mer explicit och försöker vara lättare att läsa på bekostnad av mer verbos kod. Överlagring av funktioner kommer nog aldrig komma till Rust pga. det tankesättet.
Känner du till begreppet data orienterad design?
Att det är lättare att läsa kod utan att överlagring tror jag beror på ddd (Domain driven design) och det stämmer för dör styr domänen kodens namngivning.
DDD är enklare att läsa men producerar mängder av kod.
DOD genererar inte alls lika mycket kod och återanvändning av kod är mycket bättre. Också en orsak till att unittester inte behövs. Samma kod
körs hela tiden
Känner du till begreppet data orienterad design?
Att det är lättare att läsa kod utan att överlagring tror jag beror på ddd (Domain driven design) och det stämmer för dör styr domänen kodens namngivning.
DDD är enklare att läsa men producerar mängder av kod.
DOD genererar inte alls lika mycket kod och återanvändning av kod är mycket bättre. Också en orsak till att unittester inte behövs. Samma kod
körs hela tiden
Nu blandar du kortleken igen. Vad har DOD/DDD med överlagring av funktioner att göra?
Nu blandar du kortleken igen. Vad har DOD/DDD med överlagring av funktioner att göra?
Massor
Återkommer i ämnet
Kan säga orsaken till varför det var så "stort" för mig.
STL gick inte och använda innan move kom eftersom det var för segt.
Har faktiskt utvecklat ett helt eget stl (med tankar) från det riktiga stl, i början på 2000 talet. Andra bibliotek hade alltid med sträng klasser med referensräknare istället för att kopiera och det fanns annat som vart problematiskt med stl.
Om det är största värdet med C++11 låter det väldigt mycket som du jobbar med fel språk.
Move-semantics är hyfsat mycket ett plåster för att komma runt problem orsakade av att man inte kan luta sig mot en GC. Det ger ingen (prestanda)vinst om man jobbar med PODs och "nakna" pekare. Det senare är rätt error-prone i C++, men ett icke-problem i språk med GC.
Sen igen, om GC inte fungerar (t.ex. om du har hårda realtidskrav) så prova Rust
Move-semantics i C++ ger ett par nya sätt att skjuta sig i fötterna genom att skapa UB, motsvarande kompilerar inte i Rust tack vare borrow-checker
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Massor
Återkommer i ämnet
Ta då med en bra förklaring, för Wikisidan för DDD var väldigt flummig i mitt tycke. (Har aldrig stött ihop med begreppet.)
Är sökbar kod viktig för dig?
Låt säga att det skall vara viktigt och hitta det mesta med att söka i koden, använda ett regex uttryck
Om sökbarhet är viktigt, hur skriver du kod så den är lätt att hitta
Jag försöker namnge saker på ett sådant sätt att det inte är nödvändigt att använda regex för att hitta dem. Allt skall vara unikt så att enkla sökningar hittar det som eftersöks. Appendix som "iVar" och "iVar2" är en no-no eftersom en sökning på "iVar" även hittar "iVar2". Då bör man namnge med mer eftertanke, eller i värsta fall peta in tvåan mitt i "iVarBetter" "iVar2Better". Här kan man ju i värsta fall nyttja den ungerska notationen "iVar" "i2Var" i brist på bättre fantasi. (Tvåan är ett exempel, den gäller för alla "påhäng" på tidigare namn.)
Namngivningen är en del av dokumentationen, den kan alltid förbättras. Hur man organiserar namngivning i 1M LOC lämnar jag åt proffsen att sova på.
Då jag kör Gentoo så har jag all källkod vid mina fingrar och jag är ofta inne och tittar på hur saker är gjorda. Sökningar med grep i kodbasen är frekventa för att hitta runt och följa funktionsanrop. Här kan jag inte styra eller ens önska mig, utan får bita ihop och söka på det som erbjuds, anpassa mig och vara flexibel helt enkelt.
Några tankar om att motsätta sig nya språk. Det kan ske p.g.a. att de saknar funktionalitet, men ofta är det för att man fastnat i det som känns tryggt och bekvämt. Att lära sig ett nytt språk är förknippat med en insats som inte är obetydlig om språket skall behärskas i nivå med tidigare kunskaper. Jag tror det är här som motsättningen i bl.a. Linux-kärnan mellan C och Rust mest gror, det gamla gänget ser helt enkelt det nya som ett hot som ruckar det trygga och bekväma. Det var inte bara den tidstypiska objektorienteringen som fick C++ att lyfta, kompatibiliteten med C var nog också en stor bidragande orsak. Tror detta har nämnts tidigare i tråden om inte annat?
C++ var ju, jämfört med idag, tämligen enkelt i början, så nyheterna har kommit inkrementellt sedan 1985. Det är något som i hög grad underlättat för programmerarna. Nya moderna språk har inte den lyxen utan måste implementera allt det som andra språk bidragit med under hela min livstid.
Nu blandar du kortleken igen. Vad har DOD/DDD med överlagring av funktioner att göra?
DOD är inte lätt men när det klickar kommer du aldrig gå tillbaka till DDD (som är det vanliga). Klickar det inte så fundera en del, sök och så. Det brukar ta ett tag för de flesta. Samma för mig. Man behöver ha saker och relatera till, kanske jämföra två olika lösningar.
Data orienterad design
Här är de primitiva typer som en modern processor har och orsaken till varför jag listar dem är för att det är de här typerna programmerare har att arbeta med. Skall processorn klara av att göra vettiga saker åt användare måste användarnas önskemål konverteras till de primitiva typer som processorn förstår.
1 - bool: 1 byte (true/false)
2 - char: 1 byte (signed, -128 to 127)
3 - unsigned char: 1 byte (0 to 255)
4 - short: 2 bytes (signed, -32,768 to 32,767)
5 - unsigned short: 2 bytes (0 to 65,535)
6 - int: 4 bytes (signed, -2,147,483,648 to 2,147,483,647)
7 - unsigned int: 4 bytes (0 to 4,294,967,295)
8 - long: 4 or 8 bytes (typically 8 on 64-bit, signed, -2^63 to 2^63-1)
9 - unsigned long: 4 or 8 bytes (typically 8 on 64-bit, 0 to 2^64-1)
10 - long long: 8 bytes (signed, -2^63 to 2^63-1)
11 - unsigned long long: 8 bytes (0 to 2^64-1)
12 - float: 4 bytes (IEEE 754 single precision)
13 - double: 8 bytes (IEEE 754 double precision)
Vad vi programmerare gör är att flytta data från och till användare i olika steg och det måste till programmerare för det är svårt att konvertera en användares verklighet till något som fungerar på datorn.
De allra flesta utvecklare skriver DDD kod och det är enklare att förstå sådan kod eftersom den koden anpassar sig efter användarens domän, alltså en mänsklig värld. DOD är svårare då det där handlar om processorns värld, programmeraren måste tänka som processorn gör.
DOD brukar först användas när programmerare tvingas till det. Det kan handla om prestanda, exempelvis är det vanligt i Spel eftersom man där måste anpassa sig efter processorn för att ha max hastighet. DOD är också viktigt för att klara av och skriva MYCKET kod, hantera komplex kod. Där handlar det framförallt att klara av och isolera delar och använda DOD för att transportera data mellan delarna. DOD är också effektivt för att skriva återanvändbar kod.
Nedan följer ett så enklast möjliga exempel för att visa hur en struct konverteras till DOD. I början är structen DDD (ett POD = Plain Old Data) som ofta används för att skicka information mellan metoder.
Steg 1
struct data {
int iId;
bool bActive;
double dValue;
};
//Serialisera data
iId >> bActive >> dValue
Skall data skickas mellan olika metoder eller delar i koden måste kod hårdkodas. Det går inte att lista ut vad det är för data som kommer annat än att kompilatorn instrueras hur structen ser ut. Alternativt att programmerare beskriver för annan programmerare.
Structen görs om för att få lite mer metadata (beskrivande data)
Steg 2
struct data {
int iIdType
int iId;
int iActiveType
bool bActive;
int iValueType
double dValue;
};
//Serialisera data
iIdType >> iId >> iActiveType >> bActive >> iValueType >> dValue
Nu har ett värde lagts till för varje _riktigt_ värde och syftet med det är att de extra värde som finns för varje variabel skall hålla information om vad det _riktiga_ värdet är. Varje värde börjar med typen och då kan det vara ett nummer för varje primitiv typ, nästa värde är det riktiga värdet.
Skall den här datan skickas mellan olika delar går det nu att skriva lite mer generell kod, det är fortfarande problem med vad varje värde skall användas till men det är tillräckligt för att en utvecklare och kunna "läsa" koden, samla in den alltså utan att kompilatorn vet eller man behöver prata med annan programmerare. Det krävs fortfarande domänkunskap. Structen är bökigare däremot så i steg tre kommer en större omskrivning. Tog hjälp av AI för att generera kod så därav lite annan syntax
Steg 3
// Enum to represent the supported primitive types
typedef enum {
TYPE_INT,
TYPE_BOOL,
TYPE_DOUBLE,
TYPE_CHAR,
TYPE_SHORT,
TYPE_UNSIGNED_INT,
TYPE_LONG,
TYPE_UNSIGNED_LONG,
TYPE_LONG_LONG,
TYPE_UNSIGNED_LONG_LONG,
TYPE_FLOAT,
TYPE_LONG_DOUBLE
} TypeTag;
// Union to hold the value of any primitive type
typedef union {
int i;
bool b;
double d;
char c;
short s;
unsigned int ui;
long l;
unsigned long ul;
long long ll;
unsigned long long ull;
float f;
long double ld;
} PrimitiveValue;
// Struct to represent a variant (value + type)
typedef struct {
TypeTag type;
PrimitiveValue value;
} Variant;
// Updated struct using the custom variant
typedef struct {
Variant id;
Variant active;
Variant value;
} CustomVariantData;
// Helper function to initialize a Variant
Variant CreateVariant(TypeTag type, PrimitiveValue value) {
Variant var;
var.type = type;
var.value = value;
return var;
}
// Helper function to get type name as a string (for debugging)
const char* GetTypeName(TypeTag type) {
switch (type) {
case TYPE_INT: return "int";
case TYPE_BOOL: return "bool";
case TYPE_DOUBLE: return "double";
case TYPE_CHAR: return "char";
case TYPE_SHORT: return "short";
case TYPE_UNSIGNED_INT: return "unsigned int";
case TYPE_LONG: return "long";
case TYPE_UNSIGNED_LONG: return "unsigned long";
case TYPE_LONG_LONG: return "long long";
case TYPE_UNSIGNED_LONG_LONG: return "unsigned long long";
case TYPE_FLOAT: return "float";
case TYPE_LONG_DOUBLE: return "long double";
default: return "unknown";
}
}
Här har en eget variant objekt skapats, det håller en typ och ett värde. Nu kan man börja bygga kod kring detta typsystemet och därmed öka återanvändningen. Investerad tid i kod behöver inte kastas bort. Programmerare behöver inte diskuera med varandra eller skriva dokumentation om hur olika data ser ut och skickas.
Lösningen kräver fortfarande domänkunskap, det enda vi trollat bort är vi kan ersätta POD (plain old data) objekt med ett enda (en lista med varianter). Men principen handlar om det ovanstående. Det går att öka på i flera steg men då blir det lite mer komplext här och mycket mer kod.
Poängen med DOD är annars att anpassa lösningar så att de är anpassade efter hur datorer tänker. Så länge kod är anpassad efter det så exploderar den inte i storlek. Med en del träning kan utvecklare göra otroligt dynamiska lösnignar med relativt lite kod. Och eftersom återanvändningen är så hög blir koden snabbt säker. Det här är också orsaken till varför DOD kod inte behöver unittestas.
Ett till om DOD eller rättare sagt en video som om man ser den första gången så bli inte förvånad om man inte fattar något alls.
Videon bör ses flera gånger
Mike Acton är svår i videon och orsaken är att han inte är någon talare, han är ingenjör. Det är stor skillnad på att vara verbalt begåvad och talangfull programmerare.
För varje duktig talare inom programmering går det minst 10, kanske 100 introverta programmerare som är verbala katastrofer men bättre på att programmera.
Viktigt att förstå det när man läser på eller tittar på tutorials och annat för att lära sig.
Mike Acton förstår inte att det som är lätt för honom är supersvårt för andra och han vräker ut tips i videon utan att det följer något mönster. Så för att hänga med så titta flera gånger.
Videon är 10 år gammal med, det han säger är inte samma idag för mycket har blivit bättre sett till kod.
OK, så om en databaspost har värdet "EhundraTrettionTre" så låter man datorn hantera detta och omvandla detta till 133, istället för att påverka processerna kring datainsamlingen där någon kanske borde konvertera detta själv innan posten registreras, vilket kan bli väldigt ineffektivt i den änden.
DDD låter också som något väldigt grundläggande som någon professor satt sitt namn på. Funktionen "itoa(int)" (integer to ascii) som behövs för att mata ut 133 på en skärm måste räkna ut varje siffra 1 3 3 för att kunna presentera detta för användaren., det vore väldigt ineffektivt att generellt lagra entalen i datorn.
Så att konvertera data är kort och gott ett nödvändigt ont för att effektivisera andra delar av datahanteringen. När du nämner "domänen" vilket område syftar du på då? Den samlade gruppen av data och dess hantering?
OK, så om en databaspost har värdet "EhundraTrettionTre" så låter man datorn hantera detta och omvandla detta till 133, istället för att påverka processerna kring datainsamlingen där någon kanske borde konvertera detta själv innan posten registreras, vilket kan bli väldigt ineffektivt i den änden.
DDD låter också som något väldigt grundläggande som någon professor satt sitt namn på. Funktionen "itoa(int)" (integer to ascii) som behövs för att mata ut 133 på en skärm måste räkna ut varje siffra 1 3 3 för att kunna presentera detta för användaren., det vore väldigt ineffektivt att generellt lagra entalen i datorn.
Så att konvertera data är kort och gott ett nödvändigt ont för att effektivisera andra delar av datahanteringen. När du nämner "domänen" vilket område syftar du på då? Den samlade gruppen av data och dess hantering?
Inte säker på att jag förstår din text så ursäktar för att jag misstolkat.
En dator (processorn) vet inte vad text är. Skriver du siffran "10" har en dator ingen aning om att det är siffran 10. Man måste konvertera till något som kan användas av processorn och då har de flesta operativsystem ett system där varje tecken har en kod (ASCII systemet). Brukar också följa med generell kod i de flesta språken, exempelvis "atoi" för C. Den metoden läser av varje byte i texten "10" och gör om det till ett tal dagorn förstår. Eller tvärtom "itoa" så gör det om till något användaren förstår.
Står det då "EhundraTrettionTre" i en datapost så förstår vi det för de flesta kan läsa text, de flesta har lärt sig det i skolan. Text för människor är en teknik för att transportera information så vi förstår varandra. Vi måste ha ett transportformat för vi kan inte läsa varandras tankar.
DDD låter också som något väldigt grundläggande som någon professor satt sitt namn på.
Går säkert att söka fram hur det växt fram men ordet "domän" som det bygger på, synonym för domän är
kronogods, jordagods, större lantegendom; jordområde, skogsområde, egendom, mark, område; verksamhetsfält, fack, specialitet, genre, gebit, revir, fögderi;
Domän är brett och som du skrev i någon tidigare post, "flummigt" tror jag. DDD är ofta hårdkodade lösningar för att snabbt lösa någon specifik funktionalitet, ofta företag som anlitar konsulter och de har begränsad tid att lösa företagets problem (något i det företagets "domän").
DDD används då eftersom det gör att företag och utvecklare kan prata med varandra. Utvecklaren är inte bara utvecklare utan behöver också sätta sig in i hur företaget arbetar. När utvecklaren vet det kan utvecklaren skapa en lösning som förhoppningsvis förenklar.
DOD är inte lätt men när det klickar kommer du aldrig gå tillbaka till DDD (som är det vanliga). Klickar det inte så fundera en del, sök och så. Det brukar ta ett tag för de flesta. Samma för mig. Man behöver ha saker och relatera till, kanske jämföra två olika lösningar.
Data orienterad design
Här är de primitiva typer som en modern processor har och orsaken till varför jag listar dem är för att det är de här typerna programmerare har att arbeta med. Skall processorn klara av att göra vettiga saker åt användare måste användarnas önskemål konverteras till de primitiva typer som processorn förstår.
1 - bool: 1 byte (true/false)
2 - char: 1 byte (signed, -128 to 127)
3 - unsigned char: 1 byte (0 to 255)
4 - short: 2 bytes (signed, -32,768 to 32,767)
5 - unsigned short: 2 bytes (0 to 65,535)
6 - int: 4 bytes (signed, -2,147,483,648 to 2,147,483,647)
7 - unsigned int: 4 bytes (0 to 4,294,967,295)
8 - long: 4 or 8 bytes (typically 8 on 64-bit, signed, -2^63 to 2^63-1)
9 - unsigned long: 4 or 8 bytes (typically 8 on 64-bit, 0 to 2^64-1)
10 - long long: 8 bytes (signed, -2^63 to 2^63-1)
11 - unsigned long long: 8 bytes (0 to 2^64-1)
12 - float: 4 bytes (IEEE 754 single precision)
13 - double: 8 bytes (IEEE 754 double precision)
Vad vi programmerare gör är att flytta data från och till användare i olika steg och det måste till programmerare för det är svårt att konvertera en användares verklighet till något som fungerar på datorn.
De allra flesta utvecklare skriver DDD kod och det är enklare att förstå sådan kod eftersom den koden anpassar sig efter användarens domän, alltså en mänsklig värld. DOD är svårare då det där handlar om processorns värld, programmeraren måste tänka som processorn gör.
DOD brukar först användas när programmerare tvingas till det. Det kan handla om prestanda, exempelvis är det vanligt i Spel eftersom man där måste anpassa sig efter processorn för att ha max hastighet. DOD är också viktigt för att klara av och skriva MYCKET kod, hantera komplex kod. Där handlar det framförallt att klara av och isolera delar och använda DOD för att transportera data mellan delarna. DOD är också effektivt för att skriva återanvändbar kod.
Nedan följer ett så enklast möjliga exempel för att visa hur en struct konverteras till DOD. I början är structen DDD (ett POD = Plain Old Data) som ofta används för att skicka information mellan metoder.
Steg 1
struct data {
int iId;
bool bActive;
double dValue;
};
//Serialisera data
iId >> bActive >> dValue
Skall data skickas mellan olika metoder eller delar i koden måste kod hårdkodas. Det går inte att lista ut vad det är för data som kommer annat än att kompilatorn instrueras hur structen ser ut. Alternativt att programmerare beskriver för annan programmerare.
Structen görs om för att få lite mer metadata (beskrivande data)
Steg 2
struct data {
int iIdType
int iId;
int iActiveType
bool bActive;
int iValueType
double dValue;
};
//Serialisera data
iIdType >> iId >> iActiveType >> bActive >> iValueType >> dValue
Nu har ett värde lagts till för varje _riktigt_ värde och syftet med det är att de extra värde som finns för varje variabel skall hålla information om vad det _riktiga_ värdet är. Varje värde börjar med typen och då kan det vara ett nummer för varje primitiv typ, nästa värde är det riktiga värdet.
Skall den här datan skickas mellan olika delar går det nu att skriva lite mer generell kod, det är fortfarande problem med vad varje värde skall användas till men det är tillräckligt för att en utvecklare och kunna "läsa" koden, samla in den alltså utan att kompilatorn vet eller man behöver prata med annan programmerare. Det krävs fortfarande domänkunskap. Structen är bökigare däremot så i steg tre kommer en större omskrivning. Tog hjälp av AI för att generera kod så därav lite annan syntax
Steg 3
// Enum to represent the supported primitive types
typedef enum {
TYPE_INT,
TYPE_BOOL,
TYPE_DOUBLE,
TYPE_CHAR,
TYPE_SHORT,
TYPE_UNSIGNED_INT,
TYPE_LONG,
TYPE_UNSIGNED_LONG,
TYPE_LONG_LONG,
TYPE_UNSIGNED_LONG_LONG,
TYPE_FLOAT,
TYPE_LONG_DOUBLE
} TypeTag;
// Union to hold the value of any primitive type
typedef union {
int i;
bool b;
double d;
char c;
short s;
unsigned int ui;
long l;
unsigned long ul;
long long ll;
unsigned long long ull;
float f;
long double ld;
} PrimitiveValue;
// Struct to represent a variant (value + type)
typedef struct {
TypeTag type;
PrimitiveValue value;
} Variant;
// Updated struct using the custom variant
typedef struct {
Variant id;
Variant active;
Variant value;
} CustomVariantData;
// Helper function to initialize a Variant
Variant CreateVariant(TypeTag type, PrimitiveValue value) {
Variant var;
var.type = type;
var.value = value;
return var;
}
// Helper function to get type name as a string (for debugging)
const char* GetTypeName(TypeTag type) {
switch (type) {
case TYPE_INT: return "int";
case TYPE_BOOL: return "bool";
case TYPE_DOUBLE: return "double";
case TYPE_CHAR: return "char";
case TYPE_SHORT: return "short";
case TYPE_UNSIGNED_INT: return "unsigned int";
case TYPE_LONG: return "long";
case TYPE_UNSIGNED_LONG: return "unsigned long";
case TYPE_LONG_LONG: return "long long";
case TYPE_UNSIGNED_LONG_LONG: return "unsigned long long";
case TYPE_FLOAT: return "float";
case TYPE_LONG_DOUBLE: return "long double";
default: return "unknown";
}
}
Här har en eget variant objekt skapats, det håller en typ och ett värde. Nu kan man börja bygga kod kring detta typsystemet och därmed öka återanvändningen. Investerad tid i kod behöver inte kastas bort. Programmerare behöver inte diskuera med varandra eller skriva dokumentation om hur olika data ser ut och skickas.
Lösningen kräver fortfarande domänkunskap, det enda vi trollat bort är vi kan ersätta POD (plain old data) objekt med ett enda (en lista med varianter). Men principen handlar om det ovanstående. Det går att öka på i flera steg men då blir det lite mer komplext här och mycket mer kod.
Poängen med DOD är annars att anpassa lösningar så att de är anpassade efter hur datorer tänker. Så länge kod är anpassad efter det så exploderar den inte i storlek. Med en del träning kan utvecklare göra otroligt dynamiska lösnignar med relativt lite kod. Och eftersom återanvändningen är så hög blir koden snabbt säker. Det här är också orsaken till varför DOD kod inte behöver unittestas.
Hmm, har inte du helt missförstått vad som menas med både DDD och med DOD?
Det är absolut inte normalfallet att folk skriver DDD. DDD rätt gjort kräver en rätt ingående dialog mellan programmerare och domänexperter samt en vilja från båda sidor att "förstå tillräckligt mycket om den 'andra' sidan för att kunna etablera en för projektet gemensam vokabulär".
Rätt gjort är DDD otroligt värdefullt, ju mer komplex domän ju mer värdefullt då ökad komplexitet i domänen lär leda till att programmerarna oftare missförstår problemet tillräckligt mycket för att skriva ett program som är allt annat än optimalt för de som i slutändan ska använda systemet.
Även om den som ursprungligen verkar myntat order "Domain Driven Design" också var en stark förespråkare för OOP så finns egentligen inget i DDD som kräver OOP, det kan absolut appliceras om man jobbar mer funktionellt och om man jobbar med DOD.
Går att hitta initiala boken om DDD gratis online här, den är absolut värd att läsas!!!
https://fabiofumarola.github.io/nosql/readingMaterial/Evans03...
Sen till DOD. Vet inte vad det du visar kallas, men den enklaste översättning av din initiala datarepresentation skulle jag översätta så här till DOD
struct data {
int iId;
bool bActive;
double dValue;
};
skulle bli
struct data {
int id_;
};
struct data_storage {
std::vector<int> iIds;
std::vector<bool> bActives;
std::vector<double> dValues;
};
Varje "data instans" håller bara redan på sin position i "data_storage". Notera också att g++, clang++ och MSVC++ alla optimerar std::vector<bool> till att använda en bit, inte en hel byte, per datapunkt.
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Sen till DOD. Vet inte vad det du visar kallas, men den enklaste översättning av din initiala datarepresentation skulle jag översätta så här till DOD
Det finns gott om personer på nätet som försöker producera content, detta gör de för att locka läsare/tittare. Har då någon spelprogrammerare visat hur de optimerar koden så tror andra som bara vill producera content att "jaha det är så det är",
Det du visar är typiskt, exempel på hur de konverterar data för att datan skall bli cachevänlig i spel och då man har väldigt många objekt som de ofta har när de ritar upp nätverket för en bild.
DOD är också flummigt, finns vad jag vet inget "det här är DOD"
Annars så det jag försökte visa var hur man kan plocka bort POD structs och skriva generell kod
det går att utöka
enum enumType
{
eTypeUnknown = eTypeNumberUnknown,
eTypeBool = eTypeNumberBool | eTypeGroupBoolean | eTypeGroupSize08,
eTypeInt8 = eTypeNumberInt8 | eTypeGroupInteger | eTypeGroupSize08 | eTypeGroupSigned,
eTypeInt16 = eTypeNumberInt16 | eTypeGroupInteger | eTypeGroupSize16 | eTypeGroupSigned,
eTypeInt32 = eTypeNumberInt32 | eTypeGroupInteger | eTypeGroupSize32 | eTypeGroupSigned,
eTypeInt64 = eTypeNumberInt64 | eTypeGroupInteger | eTypeGroupSize64 | eTypeGroupSigned,
eTypeInt128 = eTypeNumberInt128 | eTypeGroupInteger | eTypeGroupSize128,
eTypeInt256 = eTypeNumberInt256 | eTypeGroupInteger | eTypeGroupSize256,
eTypeInt512 = eTypeNumberInt512 | eTypeGroupInteger | eTypeGroupSize512,
eTypeUInt8 = eTypeNumberUInt8 | eTypeGroupInteger | eTypeGroupSize08,
eTypeUInt16 = eTypeNumberUInt16 | eTypeGroupInteger | eTypeGroupSize16,
eTypeUInt32 = eTypeNumberUInt32 | eTypeGroupInteger | eTypeGroupSize32,
eTypeUInt64 = eTypeNumberUInt64 | eTypeGroupInteger | eTypeGroupSize64,
eTypeUInt128 = eTypeNumberUInt128 | eTypeGroupInteger | eTypeGroupSize128,
eTypeUInt256 = eTypeNumberUInt256 | eTypeGroupInteger | eTypeGroupSize256,
eTypeUInt512 = eTypeNumberUInt512 | eTypeGroupInteger | eTypeGroupSize512,
eTypeCFloat = eTypeNumberFloat | eTypeGroupDecimal | eTypeGroupSize32,
eTypeCDouble = eTypeNumberDouble | eTypeGroupDecimal | eTypeGroupSize64,
eTypePointer = eTypeNumberPointer,
eTypeGuid = eTypeNumberGuid | eTypeGroupBinary | eTypeGroupSize128,
eTypeBinary = eTypeNumberBinary | eTypeGroupBinary,
eTypeString = eTypeNumberString | eTypeGroupString,
eTypeUtf8String = eTypeNumberUtf8String | eTypeGroupString,
eTypeWString = eTypeNumberWString | eTypeGroupString,
eTypeUtf32String = eTypeNumberUtf32String | eTypeGroupString,
eTypeJson = eTypeNumberJson | eTypeGroupString,
eTypeXml = eTypeNumberXml | eTypeGroupString,
eTypeVoid = eTypeNumberVoid,
eTypeBit = eTypeNumberBit | eTypeGroupBoolean,
eTypeRBinary = eTypeNumberBinary | eTypeGroupBinary | eTypeDetailReference,
eTypeRString = eTypeNumberString | eTypeGroupString | eTypeDetailReference,
eTypeRUtf8String = eTypeNumberUtf8String | eTypeGroupString | eTypeDetailReference,
eTypeRWString = eTypeNumberUtf8String | eTypeGroupString | eTypeDetailReference
};
Ett till om DOD eller rättare sagt en video som om man ser den första gången så bli inte förvånad om man inte fattar något alls.
Videon bör ses flera gånger
https://www.youtube.com/watch?v=rX0ItVEVjHc
Mike Acton är svår i videon och orsaken är att han inte är någon talare, han är ingenjör. Det är stor skillnad på att vara verbalt begåvad och talangfull programmerare.
För varje duktig talare inom programmering går det minst 10, kanske 100 introverta programmerare som är verbala katastrofer men bättre på att programmera.
Viktigt att förstå det när man läser på eller tittar på tutorials och annat för att lära sig.
Mike Acton förstår inte att det som är lätt för honom är supersvårt för andra och han vräker ut tips i videon utan att det följer något mönster. Så för att hänga med så titta flera gånger.
Videon är 10 år gammal med, det han säger är inte samma idag för mycket har blivit bättre sett till kod.
Är fortfarande helt klart ett (prestanda)värde i att använda DOD, men en del saker i den videon har inte riktigt åldrats superväl.
Videon spelades in när PS4/XBO generationen, d.v.s. AMD Jaguar, var det som satt i konsolerna. Där var helt klart 200 cykler mot RAM en rejäl flaskhals då out-of-order fönstret på den CPUn i storleksordningen ~60 instruktioner + att det som mest gick att ha ~16 utstående minnesoperationer. I.e. det gick inte alls att "gömma" latens på 200 cykler.
Latens mot RAM är rätt snarlik idag som den var då, men dagens CPUer har ett out-of-order fönster på närmare 500 cykler (x86_64) medan de senaste ARM64 modellerna ligger på 600-700 cykler (samtidigt som de är lägre klockade -> så lägre latens mot RAM räknat i cykler). Det går också att hålla 60 eller fler samtida loads "in-flight" samtidigt.
Så vinsten att gå till DOD är idag långt mindre jämfört med tidigare om man primärt optimerar för cache-missar, men det är självklart fortfarande en vinst. Däremot finns ju andra vinster med att gå till SOA, som att det möjliggör för effektiv användning av SIMD-optimeringar.
Vidare, om man har "tillräckligt mycket data" gör DOD också det lättare att använda flera CPU-kärnor vid beräkningar.
Men som med nästan allt, DOD är ingen silverkula. Det är väldigt värdefullt om man har relativt mycket data som man gör frekvent och relativt enkla transformer på.
Det är rätt bortkastad optimering om man får data en relativt små batcher via någon I/O-enhet (databas, nätverk, filsystem) då man ändå kommer vara primärt I/O-bunden. Också bortkastat om transformen är relativt dyr, i det läget kommer man vara "compute-bound" och prestanda där kommer inte öka meningsfull genom att få bort några cache-missar vid inläsning/skrivning av indata/utdata.
Videon är ändå helt klart sevärd! En länk som ger lite konkreta exempel för den som hellre vill läsa än att se en video i ämnet är t.ex. denna
https://hellocplusplus.com/data-oriented-design/
och denna om man vill läsa en hel bok
https://www.dataorienteddesign.com/dodbook/dodmain.html
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Det finns gott om personer på nätet som försöker producera content, detta gör de för att locka läsare/tittare. Har då någon spelprogrammerare visat hur de optimerar koden så tror andra som bara vill producera content att "jaha det är så det är",
Det du visar är typiskt, exempel på hur de konverterar data för att datan skall bli cachevänlig i spel och då man har väldigt många objekt som de ofta har när de ritar upp nätverket för en bild.
DOD är också flummigt, finns vad jag vet inget "det här är DOD"
Annars så det jag försökte visa var hur man kan plocka bort POD structs och skriva generell kod
det går att utöka
enum enumType
{
eTypeUnknown = eTypeNumberUnknown,
eTypeBool = eTypeNumberBool | eTypeGroupBoolean | eTypeGroupSize08,
eTypeInt8 = eTypeNumberInt8 | eTypeGroupInteger | eTypeGroupSize08 | eTypeGroupSigned,
eTypeInt16 = eTypeNumberInt16 | eTypeGroupInteger | eTypeGroupSize16 | eTypeGroupSigned,
eTypeInt32 = eTypeNumberInt32 | eTypeGroupInteger | eTypeGroupSize32 | eTypeGroupSigned,
eTypeInt64 = eTypeNumberInt64 | eTypeGroupInteger | eTypeGroupSize64 | eTypeGroupSigned,
eTypeInt128 = eTypeNumberInt128 | eTypeGroupInteger | eTypeGroupSize128,
eTypeInt256 = eTypeNumberInt256 | eTypeGroupInteger | eTypeGroupSize256,
eTypeInt512 = eTypeNumberInt512 | eTypeGroupInteger | eTypeGroupSize512,
eTypeUInt8 = eTypeNumberUInt8 | eTypeGroupInteger | eTypeGroupSize08,
eTypeUInt16 = eTypeNumberUInt16 | eTypeGroupInteger | eTypeGroupSize16,
eTypeUInt32 = eTypeNumberUInt32 | eTypeGroupInteger | eTypeGroupSize32,
eTypeUInt64 = eTypeNumberUInt64 | eTypeGroupInteger | eTypeGroupSize64,
eTypeUInt128 = eTypeNumberUInt128 | eTypeGroupInteger | eTypeGroupSize128,
eTypeUInt256 = eTypeNumberUInt256 | eTypeGroupInteger | eTypeGroupSize256,
eTypeUInt512 = eTypeNumberUInt512 | eTypeGroupInteger | eTypeGroupSize512,
eTypeCFloat = eTypeNumberFloat | eTypeGroupDecimal | eTypeGroupSize32,
eTypeCDouble = eTypeNumberDouble | eTypeGroupDecimal | eTypeGroupSize64,
eTypePointer = eTypeNumberPointer,
eTypeGuid = eTypeNumberGuid | eTypeGroupBinary | eTypeGroupSize128,
eTypeBinary = eTypeNumberBinary | eTypeGroupBinary,
eTypeString = eTypeNumberString | eTypeGroupString,
eTypeUtf8String = eTypeNumberUtf8String | eTypeGroupString,
eTypeWString = eTypeNumberWString | eTypeGroupString,
eTypeUtf32String = eTypeNumberUtf32String | eTypeGroupString,
eTypeJson = eTypeNumberJson | eTypeGroupString,
eTypeXml = eTypeNumberXml | eTypeGroupString,
eTypeVoid = eTypeNumberVoid,
eTypeBit = eTypeNumberBit | eTypeGroupBoolean,
eTypeRBinary = eTypeNumberBinary | eTypeGroupBinary | eTypeDetailReference,
eTypeRString = eTypeNumberString | eTypeGroupString | eTypeDetailReference,
eTypeRUtf8String = eTypeNumberUtf8String | eTypeGroupString | eTypeDetailReference,
eTypeRWString = eTypeNumberUtf8String | eTypeGroupString | eTypeDetailReference
};
Har absolut ingen aning vad man ska med det sista du visar till...
Däremot verkar du ha helt rätt i att det finns två hyfsat olika definitioner av vad "DOD" faktiskt är, fast du postade ju själv en video till den variant som inte alls verkar dra åt det håll du tycker är DOD.
De två varianterna kommer från
1. Paul Hickey
"Data-driven design is the practice of using real user data—such as behavior, performance metrics, and user feedback—to inform, validate, and refine design decisions. Rather than relying solely on intuition or aesthetics, this approach ensures that every design choice contributes to measurable user outcomes and business goals."
2. Mike Acton, Richard Fabian och Nitzan Wilnai
"Data Layout Optimization: Structuring data in memory to enhance cache performance, often using structures like arrays of structures (AoS) or structures of arrays (SoA).
Separation of Data and Behavior: Decoupling data from the functions that operate on it, allowing for more flexible and efficient processing.
Transformations Over Encapsulation: Focusing on the transformations applied to data rather than encapsulating data within objects, which can introduce overhead.
Systemic Thinking: Viewing the software as a series of data transformations, aligning with the idea that software exists to input, process, and output data."
Variant 2. är den enda variant av DOD jag kände till och tänkte på innan. Men fick upp den andra och trodde först det var den "andra" Hickey (Rich Hickey, som bl.a. designat Clojure) men finns uppenbarligen flera som heter Hickey i efternamn
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
- C++ och dess framtid att programmera minnessäkert - Hur går utvecklingen?1,7k
- 5G modem/router som failover8
- Dagens fynd — Diskussionstråden55k
- Signalbrus Steelseries9
- Postnord - en logistisk katastrof2,7k
- CAD Dator 7k4
- Vänta på paket-tråden!4,5k
- Övriga fynd (bara tips, ingen diskussion) — Läs första inlägget först!1,6k
- Stativhögtalare på golvet.17
- Ny dator inför BF6 15-20K7
- Köpes Köpes komponenter till enklare kontorsdator
- Säljes Acer Predator Connect W6 Router
- Säljes 2x Borderlands 4 spelkoder
- Köpes DDR4 16 GB-kit (2666+ MHz)
- Säljes Borderlands 4 Steamkod
- Säljes XFX Radeon RX 6950 XT Speedster MERC 319
- Säljes Z370 i5 8600k DDR4 RAM Z170A i5 6600k 1060 6GB 970 4GB Z407
- Säljes Lian Li UNI FAN SL-INF 120 ARGB Reverse Blade PWM Fan - 120mm, black
- Säljes I9-12900k + Rog Strix Z690-A Gaming wifi + 32GB Vengence DDR2 + Arctic Liquid Freezer II
- Säljes Samsung S25 Edge 256gb
- Systemkraven för Battlefield 6 presenterade28
- Lenovos handhållna Legion Go 2 kan avtäckas på IFA-mässan2
- ESET hittar skadeprogram som skriver sig självt25
- AMD går på djupet i RDNA 412
- Sapphires moderkort närmar sig lansering21
- Quiz: Vad kan du om gamingskärmar?71
- MSI: 533 dagar senare - knappt någon OLED-inbränning123
- Intels Nova Lake-S nära färdigställda35
- Bättre stöd för Bluetooth-headset i Windows 1146
- AMD Ryzen Threadripper 9980X & 9970X – bäst i klassen23
Externa nyheter
Spelnyheter från FZ