कोड की readability घटाने वाले visual complexity patterns (2023)
(seeinglogic.com)- कोड logically बेहतरीन हो, फिर भी उसे लंबे समय तक पढ़ना मुश्किल हो सकता है; यह लेख ऐसी थकान की वजह आंखों से दिखने वाली complexity में खोजता है
- Halstead Complexity Metrics operators और operands की संख्या गिनकर Volume और Difficulty निकालता है, और समान behavior होने पर भी ज्यादा variables और operators वाली implementation को ज्यादा कठिन मानता है
- SonarSource की Cognitive Complexity संक्षिप्त syntax, linear flow में रुकावट, और nested control flow को केंद्र में रखकर conditionals, loops, exception handling, logical operator combinations, recursion और
gotoके बोझ का मूल्यांकन करती है - variables के संदर्भ में variable shadowing, मिलते-जुलते names, लंबे variable lifetime, और अपरिचित usage patterns पाठक के लिए data flow trace करने की लागत बढ़ाते हैं
- छोटे functions, परिचित patterns, लंबी chains की grouping, सरल conditional expressions, सीमित
goto, shallow nesting, अलग पहचान वाले variable names, और छोटे variable lifetime—ये language और formatting से परे लागू होने वाले readability standards बनते हैं
Code readability metrics की सीमाएं और लक्ष्य
- code readability के लिए व्यापक रूप से इस्तेमाल और सहमति वाला कोई एक metric नहीं है
- संदर्भ के लिए उपलब्ध सामग्री अक्सर academic papers हैं जो वास्तविक दुनिया में व्यापक रूप से इस्तेमाल नहीं होते, या opinions जैसी है; इसलिए code review में तुरंत इस्तेमाल हो सकने वाले ज्यादा ठोस discussion tools की जरूरत थी
- लक्ष्य नया metric invent करना नहीं, बल्कि code पढ़ने में आसान है या नहीं, इस पर बात करते समय हर कोई इस्तेमाल कर सके ऐसे visual patterns इकट्ठा करना है
- समीक्षा में वे measurements या ideas शामिल थे जो इन शर्तों को पूरा करते थे
- source code snippet या single function पर लागू हो सकें
- Cyclomatic Complexity की तरह implementation algorithm से अलग करना मुश्किल core complexity पर ही focus न करें
- variable name length, whitespace, indentation, bracket placement जैसे सतही style elements तक सीमित न रहें
Halstead Complexity Metrics
- Maurice Halstead ने 1970s के अंत में source code के empirical measurements बनाने के लिए Halstead Complexity Metrics प्रस्तावित किया
- यह metric language और platform से परे लागू किया जा सकता है, और implemented algorithm के बजाय code किस रूप में लिखा गया है, इस पर focus करता है
- मुख्य measurements operators और operands पर आधारित चार counts हैं
- unique operators की संख्या
n1 - unique operands की संख्या
n2 - total operators की संख्या
N1 - total operands की संख्या
N2
- unique operators की संख्या
- Halstead ने इनके आधार पर program के length, volume, difficulty जैसे related metrics बनाए, और implementation में मौजूद bugs की संख्या estimate करने वाली value तक निकालने की कोशिश की
- intuitive तौर पर, operators जितने ज्यादा होंगे, potential interactions उतने ज्यादा सोचने पड़ेंगे; operands जितने ज्यादा होंगे, data flow possibilities समझना उतना कठिन होगा
-
JavaScript उदाहरण
- same even/odd check function में भी
ifऔरreturnवाला simple implementation कम operators और operands रखता है - 4 unique operators, 7 total operators
- 5 unique operands, 6 total operands
Volume33.30,Difficulty2.50- array,
Number, comparison expression, और index इस्तेमाल करने वाली implementation में ज्यादा operators और operands होते हैं - 7 unique operators, 10 total operators
- 9 unique operands, 12 total operands
Volume71.35,Difficulty3.75- पहली implementation आंखों से देखने पर भी ज्यादा simple है, और Halstead के
VolumeऔरDifficultyvalues भी इसे support करते हैं - drawback यह है कि सभी languages में किसे operator और operand माना जाए, यह साफ नहीं होता; measurement के लिए किसी specific tool या implementation को तय करके consistently इस्तेमाल करना बेहतर है
- same even/odd check function में भी
-
Halstead से मिलने वाले practical patterns
- function जितना छोटा और variables जितने कम होंगे, आम तौर पर पढ़ना उतना आसान होगा
- language-specific operators या syntactic sugar पाठक पर extra burden डालते हैं, इसलिए उनका overuse न करना बेहतर है
map,reduce,filterजैसे functional components, lambdas, iterators, comprehensions को लंबी chain में जोड़ने से concise होने के बावजूद readability घट सकती है- ऐसी लंबी chains JavaScript और Rust में, या Python में
itertoolsमें बहुत गहराई तक गए code में ज्यादा दिख सकती हैं
Cognitive Complexity के अनुसार पढ़ने की कठिनाई
- SonarSource द्वारा बनाई गई Cognitive Complexity reading difficulty को ज्यादा accurately पकड़ने वाला metric है
- इस metric के तीन core ideas हैं
- statements को combine करने वाला shorthand syntax difficulty घटाता है
- linear flow से हर deviation difficulty बढ़ाता है
- nested control flow difficulty बढ़ाता है
- नाम scientific या objective metric जैसा सुनाई देता है, इस पर criticism है, लेकिन practical तौर पर इसे effective heuristic माना जा सकता है
-
shorthand syntax की density की समस्या
if (a != null) { myObj = a.myObj; }की तुलना मेंMyObj myObj = a?.myObj;जैसा shorthand syntax छोटा होता है और पढ़ने में कम समय लेता है- लेकिन दोनों code वास्तव में पूरी तरह समान नहीं हो सकते
- पहले में
myObj,a.myObjयाnullबनता है - दूसरे में
myObj,a.myObjयाundefinedबनता है - TypeScript या Rust जैसी strong type checking वाली languages भी missing cases की संभावना घटाती हैं, लेकिन हर case सही तरीके से handle होगा इसकी guarantee नहीं देतीं
- plain JavaScript जैसी कमजोर type checking support वाली जगहों पर ऐसे corner cases handle न होने की संभावना ज्यादा होती है
- shorthand syntax लिखने और पढ़ने में आसान हो सकता है, लेकिन conciseness और density के बीच trade-off होता है
-
linear flow तोड़ने वाले elements
- बिना conditionals वाला linear code, conditionals वाले code की तुलना में skim करना आसान होता है
- Cognitive Complexity conditionals, loops,
gotoके अलावा conditional macros,try/except, logical operator sequences, और recursion को भी difficulty-increasing elements मानती है switchको एक group के रूप में count किया जाता है, लेकिनelse-ifchain में हर additionalelse-ifको ज्यादा कठिन माना जाता है- कारण यह है कि
else-ifमें हर branch पर दो या अधिक comparisons हो सकते हैं - हालांकि
switchका fall-through और missingbreakभी reading difficulty बढ़ा सकते हैं - conditional expression के अंदर एक ही logical operator को sequence में लिखना और
&&,||,!को mix करना अलग-अलग difficulty दिखाते हैं debug || verbose || consoleModeएक simple conditional expression हैdebug || (verbose && consoleMode)operators mix होने की वजह से पढ़ने में ज्यादा कठिन हैdebug || !(verbose && consoleMode)negation शामिल होने से और complex हो जाता है
-
exception handling और goto
try/catchCognitive Complexity में difficulty बढ़ाते हैं, लेकिन multiplecatchblocks को singlecatchसे ज्यादा कठिन नहीं माना जाता, औरtryतथाfinallyको ignore किया जाता है- exception throw करना खुद भी reading cost बना सकता है
- exception handling function boundaries पार कर जाए तो संबंधित functions की complexity आपस में उलझ जाती है
- पाठक को ढूंढना पड़ता है कि वह exception कहां catch हो रहा है
gotoको आम तौर पर difficulty बढ़ाने वाला element count किया जाता है- हालांकि error condition में resources release करते हुए function से बाहर निकलने वाला
goto outयाgoto donepattern कुछ experts के अनुसार उपयोगी हो सकता है - इसके विपरीत, loop boundary को ऐसे पार करने वाला
gotoजिसेcontinueयाbreakसे express नहीं किया जा सकता, पाठक को नया control flow फिर से reconstruct करने पर मजबूर करता है, इसलिए reading burden ज्यादा होता है
nesting और function shape
- अगर conditional itself पढ़ना कठिन है, तो nested conditionals और भी कठिन होते हैं
- Cognitive Complexity conditionals/loops के अपने score के अलावा हर nesting level पर additional difficulty जोड़ती है
- इसी idea को “Level of Indentation” या “Bumpy Road” जैसे नामों से भी जाना जाता है
- nesting 2 levels से आगे जाए तो पढ़ना खास तौर पर कठिन हो जाता है, और early return से nesting घटाने वाला code ज्यादा flat पढ़ा जाता है
- यह metric function length को directly reflect नहीं करता, लेकिन बाकी conditions समान हों तो long function short function की तुलना में पढ़ने में ज्यादा effort मांगता है
variable names, lifetime, और familiar patterns
-
अलग पहचान वाले और descriptive names
- descriptive names code क्या करना चाहता है, यह समझने में important हैं; duplicate या cryptic names उल्टा असर डालते हैं
- variable shadowing खतरनाक है
- ऐसी situation से बचना चाहिए जहां पाठक को scope rules देखकर तय करना पड़े कि कौन-सा variable इस्तेमाल हो रहा है
- visually similar identifiers से भी बचना चाहिए
iऔरj,itemऔरitemsजैसे आंखों से confuse होने वाले names mistakes करा सकते हैं- एक function में
node,_node,thisNodeजैसी same variable name की कई variants इस्तेमाल करने वाला code bugs पैदा करने में आसान pattern है
-
short variable lifetime
- live variable analysis variable के पहली बार इस्तेमाल होने वाले point से लेकर last possible use point तक का range देखता है
- variable lifetime लंबा हो तो पाठक को ज्यादा variables और possible values दिमाग में बनाए रखने पड़ते हैं
- function के top पर सभी variables declare करने की जगह, actual use से ठीक पहले declare करना lifetime घटा सकता है
- सबसे खराब case वह है जब variable कई functions में live रहता है और कई जगहों पर इस्तेमाल होता है
- अगर कई variables के lifetime practically same हैं, तो object ज्यादा appropriate हो सकता है
- अगर object सही fit नहीं है, तो values समझने के लिए पाठक को जितने functions और lines पढ़ने पड़ें, उन्हें minimize करना बेहतर है
-
लंबी chains और intermediate variables
- functional programming style variable lifetime छोटा कर देता है, लेकिन बहुत लंबी chains या callback nesting reading burden बना सकती हैं
- लंबी function chains को छोटे groups में बांटकर, अच्छे नाम वाले intermediate variables या helper functions इस्तेमाल करने से पाठक का cognitive burden घट सकता है
- intermediate variables इस्तेमाल करने वाला version थोड़ा कम efficient हो सकता है
- जब तक performance tool यह न बताए कि वही line actual bottleneck है, efficiency के ऐसे microscopic differences महत्वपूर्ण नहीं हैं
-
familiar code patterns का reuse
- familiar code और variable shapes reuse करने से पाठक पहले से known patterns पहचानता है, इसलिए पढ़ना कम मेहनत वाला होता है
- यह Principle of Least Surprise से जुड़ा है
- codebase के अंदर conditional writing style जैसे recurring forms को consistent रखना बेहतर है
- जब pattern से हटना पड़े, तो variable name या comment के जरिए difference दिखाया जा सकता है
- इस विचार को अंत तक ले जाएं तो direction template functions या generic functions का उपयोग करने की बनती है, ताकि पाठक को repeated patterns दोबारा recognize न करने पड़ें
readability बढ़ाने वाले 8 visual patterns
- Line/Operator/Operand count: छोटे functions और कम variables/operators पढ़ने में आसान होते हैं
- Novelty: function shape, operators, syntactic sugar में novelty से बचें और codebase के common patterns reuse करें
- Grouping: लंबी function chains, iterators, comprehensions को helper functions या intermediate variables से logical groups में बांटें
- Conditional simplicity: conditional expressions को यथासंभव छोटा रखें, और एक condition के अंदर अलग-अलग logical operators mix करने के बजाय same operator sequence prefer करें
- Gotos: जब तक कोई specific error-handling pattern follow न हो और alternatives worse न हों,
gotoइस्तेमाल न करें - Nesting: nested logic और बड़े indentation changes minimize करें; अगर deep nesting जरूरी हो तो उसे बड़े function के अंदर दबाने के बजाय अलग function में split करें
- Variable distinction: descriptive और visually distinct variable names इस्तेमाल करें, और variable shadowing से बचें
- Variable liveness: variable lifetime छोटा रखें, और खासकर function boundaries पार करने वाले लंबे lifetime से सावधान रहें
real codebase में दिखी समस्याएं
- जिस codebase ने mental fatigue बहुत बढ़ाई, उसमें कई anti-patterns साथ-साथ थे
- specifically, लंबे functions, कई language constructs का mix, और बहुत-सी function chains थीं जिन्हें helper functions में अलग किया जाना चाहिए था
- परिणामस्वरूप बड़े function के अंदर nested complexity और लंबे variable lifetime साथ-साथ बढ़ गए
- code और author की quality high होने के बावजूद एक या अधिक गंभीर bugs मिले
- उनमें से एक bug देखने में आसान था, लेकिन लंबी और complex function के बीच में था और उसके बारे में reason करना कठिन था, इसलिए उसके छूट जाने की संभावना ज्यादा है
- एक महीने बाद अपने code को सबसे ज्यादा पढ़ने वाला व्यक्ति अक्सर आप खुद होंगे, इसलिए readable form में लिखना सीधे practical cost से जुड़ा है
1 टिप्पणियां
Hacker News की राय
लेख में यह कहना कि map/reduce/filter chain लंबी हो या कई हों तो readability खराब होती है, बाकी बातों से बिल्कुल follow नहीं होता
ऐसा लगता है जैसे यह वही आम शिकायत है कि “क्योंकि मैं इससे परिचित नहीं हूं, इसलिए यह खराब है” जिसे चुपचाप जोड़ दिया गया हो; थोड़ा अभ्यस्त हो जाएं तो कई मामलों में यह alternatives की तुलना में पढ़ने और लिखने में कहीं आसान होता है
उदाहरण के लिए
books.filter(book => book.pageCount > 1000).map(book => book.author).distinct()से ज्यादा पढ़ने योग्य code देना मुश्किल लगता हैcomplexity metric से भी देखें तो ऐसा code लगभग किसी भी दूसरे तरीके से बेहतर है, और functional programming की basics तो सीखनी ही चाहिए. monad समझाने की जरूरत नहीं, लेकिन
mapऔरfilterको बिना सोचे-समझे नीचा दिखाने से बचने लायक familiarity तो होनी चाहिएमोटे तौर पर 5 calls वाली chain से पढ़ना मुश्किल होना शुरू होता है, और article का example भी लगभग उतनी ही लंबाई का था
“multiple” से मतलब nested chains या उस type के बदलने से था जिस पर operation हो रहा है; ऐसे मामलों में पढ़ने की speed धीमी हो जाती है
functional components elegant हो सकते हैं, लेकिन उनका overuse भी हो सकता है
chain बहुत लंबी हो जाए, conditions जटिल हो जाएं और lists/variables ज्यादा हो जाएं, तो एक बार में समझना मुश्किल हो जाता है
procedural loop में intermediate results को नाम दिया जा सकता है, और उन नामों की वजह से पहले किए गए processing को भूलकर अगले step पर focus किया जा सकता है
SELECT DISTINCT author FROM books WHERE pageCount > 1000;निजी तौर पर, मैं intermediate results को नाम देने के लिए chain को बांट देता हूं, और variable names को comments की तरह इस्तेमाल करने जैसा मानता हूं
var longBooks = books.filter(book => book.pageCount > 1000)var authorsOfLongBooks = longBooks.map(book => book.author).distinct()?- setof(Author, Book^Pages^(book_author(Book, Author), book_pages(Book, Pages), Pages > 1000), Authors).Prolog database structure के आधार पर इसे और छोटा भी किया जा सकता है
?- setof(Author, Pages^(book(_, Author, Pages), Pages > 1000), Authors).मुझे लगता है अच्छे code में मूल रूप से qualitative और literary पहलू काफी बड़े होते हैं
mathematical thinking के आदी programmers या academia के लोग, जो quantitative answers चाहते हैं, अक्सर इस बात से असहज हो जाते हैं
मुझे Dostoyevsky और Wodehouse दोनों पसंद हैं; दोनों बेहतरीन हैं, लेकिन उनके तरीके बहुत अलग हैं
coding शायद उतना खुला मैदान नहीं है, फिर भी मैंने ऐसे अच्छे codebases पर काम किया है जो quality के स्तर पर बिल्कुल अलग महसूस होते थे, और जैसे किसी नए लेखक की style की आदत पड़ने में समय लगता है, वैसे ही codebase की style का “feel” आने में भी अक्सर समय लगता है
मतलब यह था कि functions का order ऐसा रखा गया था कि file को ऊपर से नीचे पढ़ने पर narrative natural तरीके से follow हो, और implementation declarative था, जैसे reader से बात कर रहा हो
मैं pure functional programming paradigm follow करता हूं, और मुझे लगता है यह तरीका अधिक narrative style के साथ अच्छी तरह fit बैठता है
function की dependencies/inputs arguments या दूसरे pure functions तक सीमित होते हैं और output केवल return type में होता है, इसलिए complexity को step-by-step guide करना आसान होता है
hidden state जैसी complexity वाले दूसरे paradigms के उलट, विडंबना यह है कि मुझे लगता है सबसे mathematically precise paradigm ही अधिक narrative style के लिए भी सबसे अच्छा fit है
form मायने नहीं रखती; अगर लंबे development experience के बिना reasonable time में यह नहीं समझ आता कि function क्या करता है, तो वह बस bad code है
उदाहरण के लिए article में दिया ternary operator
return n % 2 === 0 ? 'Even' : 'Odd';इंसानी दिमाग को उल्टा पढ़ा जाता हुआ लगता है, और यह humans की तुलना में compiler के लिए syntax tree process करने के लिए ज्यादा suited हैकोई human mathematician इसे शायद piecewise function के रूप में लिखेगा: अगर
n mod 2 = 0तो'Even', वरना'Odd'; वह कहीं ज्यादा clear हैयह नए team members को consistent style सीखने में मदद करता है, और team style को भी काफी हद तक consistent रखता है
.editorconfigपर comment भी देखने लायक है: https://news.ycombinator.com/item?id=43333011यह pull requests में style details पर बहस करने की जरूरत कम कर देता है
लेख अच्छा है, लेकिन मुझे लगता है कि उसने code पढ़ते समय मानसिक रूप से सबसे थका देने वाले तत्व, mutability, को मिस कर दिया
किसी method को पढ़ते हुए किसी variable का अर्थ सिर्फ़ एक बार “fix” कर पाना, और बाकी चीज़ें infer करते समय उसे वैसा ही बनाए रख पाना, बहुत बड़ी सुविधा है
method की समझ 0% से 100% तक monotonically बढ़नी चाहिए; ऐसा नहीं होना चाहिए कि किसी खास iteration में loop body ने accumulator को कैसे बदला, यह उलझन हो और method को दिमाग में फिर से शुरू करना पड़े
GOTO के हानिकारक होने की असली वजह भी यही है। method के अंदर दिमागी instruction pointer को हिलाना कठिन नहीं है; समस्या यह है कि GOTO होने पर mutable variables की state जानना कठिन हो जाता है
mutable और immutable variables, दोनों उस movement में मदद भी कर सकते हैं और बाधा भी डाल सकते हैं; यह इस पर निर्भर करता है कि code उस space से कितनी साफ़ तरह match करता है
immutable variables का थोड़ा tactical लाभ है कि value बदलने या misleading तरीके से बदलने की चिंता नहीं रहती, लेकिन अनुभव में यह इतना बड़ा नहीं कि “हमेशा immutability इस्तेमाल करो” जैसा rule बनाया जाए
कभी-कभी mutability उस information space को कहीं ज़्यादा साफ़ ढंग से व्यक्त करने देती है
call site के बजाय callee के नज़रिए से देखें, तो जब कोई किसी खास line पर jump कर सकता है, उससे पहले क्या हुआ था यह backtrack नहीं किया जा सकता। वह कहीं से भी आया हो सकता है, इसलिए local analysis नहीं बल्कि global program analysis चाहिए होता है
अगर mutability GOTO complexity का असली स्रोत होती, तो
ifstatements औरforloops में भी वही समस्या होनी चाहिए थीमैं मानता हूँ कि mutability और state सीधे complexity बनाते हैं, लेकिन GOTO पूरी तरह अलग और कहीं ज़्यादा हानिकारक category है
मुझे निजी तौर पर नापसंद pattern वह है जिसमें
ifमें तुरंत return कर दिया जाता है और बाकी को implicit default path की तरह छोड़ दिया जाता हैif (n % 2 === 0) return "Even"; return "Odd";छोटा तो है, लेकिन मैंif ... return "Even"; else return "Odd";को कहीं ज़्यादा पसंद करता हूँवजह यह है कि पहला वाला asymmetric feel देता है।
"Even"और"Odd"symmetric choices हैं, इसलिएelseवाला रूप ज़्यादा intuitive हैreturn (n % 2 === 0) ? "Even" : "Odd";जैसा लिखना सबसे कम boilerplate रखता है, इसलिए सबसे readable हैजिस भाषा में ternary operator हो, उसमें यह आसानी से पहचाना जा सकना चाहिए
अनुभव में
elseextra nesting बनाता है, और normal path के immediate scope से बाहर की boundary conditions या variables को लगातार evaluate करवाने लगता है। वह पूरा context साथ रखना पड़ता हैfunction name के ठीक नीचे एक level indentation पर return visually दिख जाता है, और अगर early exit नहीं है तो guaranteed result होने का एहसास देता है
return अगर नीचे कहीं दबा हुआ हो तो कुछ अटपटा लगता है
return "Odd";कोifसे अलग दिखाने के लिए एक blank line डालूँगा, और भाषा allow करे तोifbody में braces भी जोड़ूँगाelseallow करने वाली स्थितियाँ भी होती हैं, लेकिन आम तौर पर तब जब side effects हों; सामान्यतः उसे हटाने तक refactor करने पर code ज़्यादा clear हो जाता हैcomplex code अक्सर importance या execution cost के order में बाहर निकलने वाले guard sequence में बदल जाता है, और actual function/method logic को termination conditions से अलग कर देता है
पहला तरीका नीचे flow करते हुए command के दूसरे set को execute कराता है।
returncontrol flow को तोड़ने वाला special operator है, इसलिए पहले तरीके का normal control flow दोनों cases की completeness को ठीक से capture नहीं करताidiomatic Rust में method control flow को तोड़ने वाले exceptional cases को छोड़कर
returnइस्तेमाल नहीं किया जाता, और दूसरा example आम तौर पर return statement के बिना ज़्यादा दिखता हैPython में भी आम तौर पर invalid arguments या state पर शुरुआत में early return किया जाता है, और tail position का return ही actual return value बनता है
इन conventions की वजह से complete
if-elsestructure तोड़ने पर indentedreturnexceptional situation जैसा दिखने लगता है। इस convention को follow करने परreturnstatements control flow तोड़ने के cases को छोड़कर स्वाभाविक रूप से duplicate जैसे दिखते हैं, और Rust का convention समझ आता है। हर language मेंreturn,breakके बराबर statement हैशायद सिर्फ़ मुझे ही ऐसा लगता हो, लेकिन TypeScript कभी-कभी code पढ़ना मुश्किल बना देता है
data model को कुछ हद तक “atomic” रखा जाए और developer types को सच में declare और document करने में ईमानदार हों तो ठीक है
लेकिन जब utility types से types दूसरे types से derive होने लगते हैं, और explicit types छोड़कर type inference पर भरोसा किया जाता है, तो चीज़ें जल्दी खुलने लगती हैं
4~5 levels की type indirection जैसी deep stack में यह track करना बहुत मुश्किल हो जाता है कि field कहाँ से आया। कुछ inferred होते हैं, कुछ explicit, कुछ derived types होते हैं और field aliases भी मिले होते हैं
बड़े data model और deep call stack में
function checkDogs(dogs: Dog[]) { ... }की तरह return type छोड़ा हुआ form बिल्कुल unusable और सच में पागल कर देने वाला होता हैमुख्य वजह यह है कि उस function से return होने वाले सभी paths को उस type का पालन करने के लिए force किया जाए
मैंने कई बार देखा है कि नया condition जोड़ते समय किसी और branch से थोड़ा अलग type return हो गया और regression बन गया
हालांकि variable declarations पर type लगाना मुझे बहुत valuable नहीं लगता
example में
const checkedDoggos = checkDogs([])अच्छा है, औरcheckedDoggosको function type inherit करने देना चाहिएमैं ऐसे codebase से जूझ रहा हूँ जहाँ linter
const checkedDoggos: DogBreedAndSize[] = checkDogs([])force करता है; यह काफ़ी हास्यास्पद है और खास value नहीं देताJavaScript में sure नहीं हो सकते, इसलिए stack में ऊपर-नीचे लगातार जाना और उसे दिमाग में बनाए रखना पड़ता है
“कम variables वाले छोटे functions आम तौर पर पढ़ने में आसान होते हैं” — इस बात को लेकर सावधान रहना चाहिए
मुझे यह पसंद नहीं कि readability की चर्चा सिर्फ़ micro-readability पर केंद्रित हो जाए। तब macro-readability की तुलना में micro-readability ज़्यादा महत्वपूर्ण है—इस गलत धारणा के तहत code को बहुत ज़्यादा छोटे-छोटे हिस्सों में बाँटना आसान हो जाता है
इस तरह की कट्टरता ऐसे programmers बनाती है जो जंगल नहीं, सिर्फ़ पेड़ देखते हैं, और इससे बहुत ज़्यादा inefficient code या debug करने में कठिन code पैदा होता है
APL परिवार की languages दूसरी चरम सीमा पर हैं, लेकिन असली optimum शायद बीच में कहीं है और व्यक्ति के हिसाब से काफ़ी बदल सकता है
अपरिचित code में definition पर 3–4 बार jump करना ही भारी पड़ने लगता है; हो सकता है यह मेरी समस्या हो, लेकिन यह कल्पना करना मुश्किल है कि ज़्यादातर लोग मुझसे बहुत बेहतर होंगे
.NET culture में, ख़ासकर “clean architecture” में, यह समस्या चौंकाने वाली हद तक दिखती है। किसी feature को बदलना हो या issue trace करना हो, तो वह 4 layers और 15 files में फैला होता है, और कुछ files में 60% से ज़्यादा सिर्फ़ keywords होते हैं
सीमा कहाँ खींचनी चाहिए, यह नहीं पता, लेकिन मैं आम तौर पर ऐसी एक लंबी function को पसंद करता हूँ जिसे अन्य recommendations का पालन करते हुए क्रम से पढ़ा जा सके, बजाय ऐसे code के जो इतना टुकड़ों में बँटा हो कि हर 5 lines पर ऊपर-नीचे scroll करना पड़े
types/classes के साथ भी यही बात है; सिर्फ़ इसी DTO में इस्तेमाल होने वाले 4 values वाले enum को अलग file में रखने की ज़रूरत नहीं है
लेख दिलचस्प है, लेकिन पूरी तरह संतोषजनक नहीं
यह निष्कर्ष पर बहुत जल्दी पहुँच जाता है और फिर से व्यक्तिगत पसंद पर लौट आता है। कई preferences से मैं सहमत हूँ, लेकिन लेख खुद स्पष्ट रूप से preferences से आगे जाने की कोशिश कर रहा था
“language-specific operators या syntactic sugar पाठक पर बोझ डालते हैं, इसलिए उनसे बचें” — यह बात metrics से निकलकर नहीं आती। अगर किसी function में 3 अलग-अलग operators हैं और एक language-specific operator उन तीनों को एक साथ replace कर देता है, तो function का “effort” कम हो जाता है
map/reduce/filterजैसे components भी अगर सही तरह से इस्तेमाल हों तो दूसरे operators को replace करके “volume” घटा सकते हैं, इसलिए बात दोनों दिशाओं में जा सकती है?.वाला उदाहरण JavaScript की पढ़ने में कठिन language design की बहुत language-specific diagnosis लगता है। कई languages मेंnullऔरundefinedअलग नहीं होते, इसलिए इसे अक्सर null-safe operator कहा जाता है“variable shadowing भयानक है” और “लंबी lifetime ज़्यादा variables को दिमाग़ में बनाए रखने पर मजबूर करती है” — ये दोनों बातें आपस में टकरा सकती हैं
कुछ contexts में मुझे variable shadowing बहुत पसंद है, क्योंकि यह पिछले instance को accessible बनाए रखने के बजाय उसे scope से हटा देता है
VS Code के लिए Highlight नाम का एक शानदार plugin है
custom regular expressions से code पर अलग रंग लागू किए जा सकते हैं, और आम उपयोग शायद
//TODOको पीला करना होगामैं इसे logs को fade करने के लिए इस्तेमाल करता हूँ, क्योंकि जगह-जगह logs डालने से visual noise बहुत बढ़ जाती है
मैं जिस library को maintain करता हूँ, वह
this.logger?.info('Some logs here');जैसे logs इस्तेमाल करती है, और इस पर 0.4 opacity लगाकर उन्हें background में धकेल देता हूँवे फिर भी दिखते हैं, लेकिन एक नज़र में असली business logic ज़्यादा उभरकर दिखता है
settings को इस तरह बदलकर इस्तेमाल किया जा सकता है:
"highlight.regexes": { "((?:this\\.)?(?:_)?logger(?:\\?)?.(debug|error|info|warn)[^\\)]*\\)\\;)": { "regexFlags": "gmi", "decorations": [{ "opacity": "0.4" }] } }https://marketplace.visualstudio.com/items?itemName=fabiospa...
इस बात से सहमत नहीं हूँ कि लंबी function chains या callbacks को छोटे groups में बाँटकर अच्छे नाम वाले variables इस्तेमाल करना थोड़ा कम efficient है
दोनों versions समान रूप से efficient हो सकते हैं
दोनों मामलों में वही objects allocate होते हैं, heap में store होते हैं, और garbage collection के target बनते हैं। efficiency का अंतर compiler पर निर्भर है
दूसरे version में compiler को यह देखकर कि हर variable declaration के तुरंत बाद ही इस्तेमाल होता है, उन objects को chain call की तरह scope से बाहर treat कर पाना चाहिए
बेशक, यह मानकर चल रहा हूँ कि variable type infer होने दिया गया है
असली cost तब दिखती है जब intermediate value को debugger में देखने के लिए स्पष्ट रूप से materialize किया जाता है। उदाहरण के लिए, उसे list बनाने पर ऐसी allocation हो जाती है जिसे टाला जा सकता था, और इसकी cost लगती है
“readability” को quantify करने की कोशिश अच्छी है। ऐसे approaches की बहुत ज़्यादा ज़रूरत है
अभी readability की सबसे आम definition लगभग “जो मुझे पढ़ने में आसान लगे” जैसी महसूस होती है
बहुत सारे लोगों को code दिखाकर, उनसे यह बताने वाला वाक्य चुनवाया जाए कि code क्या करता है, और समय मापा जाए, तो शायद readability के वास्तविक dimensions मिल सकते हैं
जिन problems को सबसे ज़्यादा लोग सबसे कम average time में सही हल करें, वे real world में पढ़ने में आसान code के उदाहरण बनेंगे, और ज़्यादा महत्वपूर्ण यह कि सचमुच पढ़ने में कठिन practices की पहचान करने में मदद मिल सकती है
respondents शायद “programming experience”, “paradigm X समझते हैं या नहीं” जैसे axes पर cluster होंगे, और trends बदलने के साथ results भी समय के साथ shift हो सकते हैं
आपने क्या पढ़ना और लिखना सीखा है, वही तय करता है कि आपको क्या पढ़ने में आसान लगता है
आप क्या करने की कोशिश कर रहे हैं, किसके साथ काम कर रहे हैं, coding से पहले क्या कर पाते थे, कौन-सी दूसरी languages जानते हैं—ऐसे कई factors असर डालते हैं
low-hanging fruit लेने के बाद, जैसे variable names को arbitrary, irrelevant या misleading न रखने के स्तर से आगे जाने पर, कई “readability” समस्याएँ अंततः consensus बनाने की बात हो सकती हैं
जिन खास programmers के group के साथ आप काम करना चाहते हैं, उससे परे कोई सही जवाब शायद न हो
code readability language readability जैसी है; आम तौर पर यह उन लोगों के लिए समस्या होती है जो उस language को अच्छी तरह नहीं जानते, और समय देने पर हल हो जाती है
programming की असली समस्या code complexity है, और इसका judgement अलग-अलग code snippets के metrics से नहीं किया जा सकता
समस्या function body के अंदर implementation choices से ज़्यादा functions के बीच relationships में होती है
code क्या करता है, यह पता लगाना आम तौर पर आसान होता है; मुश्किल काम उस code को modify करना या feature add करना होता है
यह कठिनाई इसलिए पैदा होती है क्योंकि abstraction की कई layers एक-दूसरे से कैसे जुड़ी हैं, यह छिपा देती हैं