2 पॉइंट द्वारा GN⁺ 2025-03-12 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • कोड 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
  • 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
    • Volume 33.30, Difficulty 2.50
    • array, Number, comparison expression, और index इस्तेमाल करने वाली implementation में ज्यादा operators और operands होते हैं
    • 7 unique operators, 10 total operators
    • 9 unique operands, 12 total operands
    • Volume 71.35, Difficulty 3.75
    • पहली implementation आंखों से देखने पर भी ज्यादा simple है, और Halstead के Volume और Difficulty values भी इसे support करते हैं
    • drawback यह है कि सभी languages में किसे operator और operand माना जाए, यह साफ नहीं होता; measurement के लिए किसी specific tool या implementation को तय करके consistently इस्तेमाल करना बेहतर है
  • 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-if chain में हर additional else-if को ज्यादा कठिन माना जाता है
    • कारण यह है कि else-if में हर branch पर दो या अधिक comparisons हो सकते हैं
    • हालांकि switch का fall-through और missing break भी 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/catch Cognitive Complexity में difficulty बढ़ाते हैं, लेकिन multiple catch blocks को single catch से ज्यादा कठिन नहीं माना जाता, और 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 done pattern कुछ 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 टिप्पणियां

 
GN⁺ 2025-03-12
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 तो होनी चाहिए

    • भाषा बेवजह तीखी लगती है, लेकिन quote किए गए वाक्य का मतलब ऐसे छोटे chain के examples से नहीं था
      मोटे तौर पर 5 calls वाली chain से पढ़ना मुश्किल होना शुरू होता है, और article का example भी लगभग उतनी ही लंबाई का था
      “multiple” से मतलब nested chains या उस type के बदलने से था जिस पर operation हो रहा है; ऐसे मामलों में पढ़ने की speed धीमी हो जाती है
      functional components elegant हो सकते हैं, लेकिन उनका overuse भी हो सकता है
    • example तो सिर्फ एक single list पर conceptually simple filter है
      chain बहुत लंबी हो जाए, conditions जटिल हो जाएं और lists/variables ज्यादा हो जाएं, तो एक बार में समझना मुश्किल हो जाता है
      procedural loop में intermediate results को नाम दिया जा सकता है, और उन नामों की वजह से पहले किए गए processing को भूलकर अगले step पर focus किया जा सकता है
    • SELECT DISTINCT author FROM books WHERE pageCount > 1000;
    • example ठीक है, लेकिन असली शिकायत शायद बहुत लंबी chains के बारे में थी
      निजी तौर पर, मैं intermediate results को नाम देने के लिए chain को बांट देता हूं, और variable names को comments की तरह इस्तेमाल करने जैसा मानता हूं
      var longBooks = books.filter(book => book.pageCount > 1000)
      var authorsOfLongBooks = longBooks.map(book => book.author).distinct()
    • लोगों ने जो SQL solutions दिए हैं वे भी अच्छे हैं, लेकिन Prolog में इसे ऐसे भी लिखा जा सकता है
      ?- 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” आने में भी अक्सर समय लगता है

    • 100% सहमत. programming के बारे में मुझे मिली सबसे अच्छी तारीफों में से एक थी कि “code कहानी की तरह पढ़ा जाता है
      मतलब यह था कि 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 है
    • अगर किसी function का higher-level goal पढ़कर समझने में 5 सेकंड से ज्यादा लगते हैं, तो मैं उसे bad code मानता हूं
      form मायने नहीं रखती; अगर लंबे development experience के बिना reasonable time में यह नहीं समझ आता कि function क्या करता है, तो वह बस bad code है
    • elegant माने जाने वाले कई syntax patterns असल में mathematics से भी कम clear लगते हैं
      उदाहरण के लिए 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 है
    • इसलिए code review महत्वपूर्ण है
      यह नए team members को consistent style सीखने में मदद करता है, और team style को भी काफी हद तक consistent रखता है
      .editorconfig पर comment भी देखने लायक है: https://news.ycombinator.com/item?id=43333011
      यह pull requests में style details पर बहस करने की जरूरत कम कर देता है
    • शायद इसी वजह से Literate Programming व्यापक रूप से स्थापित नहीं हो पाया
  • लेख अच्छा है, लेकिन मुझे लगता है कि उसने code पढ़ते समय मानसिक रूप से सबसे थका देने वाले तत्व, mutability, को मिस कर दिया
    किसी method को पढ़ते हुए किसी variable का अर्थ सिर्फ़ एक बार “fix” कर पाना, और बाकी चीज़ें infer करते समय उसे वैसा ही बनाए रख पाना, बहुत बड़ी सुविधा है
    method की समझ 0% से 100% तक monotonically बढ़नी चाहिए; ऐसा नहीं होना चाहिए कि किसी खास iteration में loop body ने accumulator को कैसे बदला, यह उलझन हो और method को दिमाग में फिर से शुरू करना पड़े
    GOTO के हानिकारक होने की असली वजह भी यही है। method के अंदर दिमागी instruction pointer को हिलाना कठिन नहीं है; समस्या यह है कि GOTO होने पर mutable variables की state जानना कठिन हो जाता है

    • सहमत नहीं। एक abstract information space होता है जिसे code model कर रहा होता है, और दिमागी instruction pointer को उसी space के भीतर चलना चाहिए
      mutable और immutable variables, दोनों उस movement में मदद भी कर सकते हैं और बाधा भी डाल सकते हैं; यह इस पर निर्भर करता है कि code उस space से कितनी साफ़ तरह match करता है
      immutable variables का थोड़ा tactical लाभ है कि value बदलने या misleading तरीके से बदलने की चिंता नहीं रहती, लेकिन अनुभव में यह इतना बड़ा नहीं कि “हमेशा immutability इस्तेमाल करो” जैसा rule बनाया जाए
      कभी-कभी mutability उस information space को कहीं ज़्यादा साफ़ ढंग से व्यक्त करने देती है
    • कुल complexity सिर्फ़ किसी ज्ञात starting point से instruction pointer को हिलाने की समस्या नहीं है
      call site के बजाय callee के नज़रिए से देखें, तो जब कोई किसी खास line पर jump कर सकता है, उससे पहले क्या हुआ था यह backtrack नहीं किया जा सकता। वह कहीं से भी आया हो सकता है, इसलिए local analysis नहीं बल्कि global program analysis चाहिए होता है
      अगर mutability GOTO complexity का असली स्रोत होती, तो if statements और for loops में भी वही समस्या होनी चाहिए थी
      मैं मानता हूँ कि 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 हो, उसमें यह आसानी से पहचाना जा सकना चाहिए
    • guard clauses/early returns developer का focus original happy path से हटाकर function के behavior को धीरे-धीरे narrow करने की तरफ ले जाते हैं
      अनुभव में else extra 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 करे तो if body में braces भी जोड़ूँगा
      else allow करने वाली स्थितियाँ भी होती हैं, लेकिन आम तौर पर तब जब side effects हों; सामान्यतः उसे हटाने तक refactor करने पर code ज़्यादा clear हो जाता है
      complex code अक्सर importance या execution cost के order में बाहर निकलने वाले guard sequence में बदल जाता है, और actual function/method logic को termination conditions से अलग कर देता है
    • asymmetry तब साफ़ दिखती है जब code को continuation/callback style या ज़्यादा complex data structure mutation तरीके में refactor किया जाता है
      पहला तरीका नीचे flow करते हुए command के दूसरे set को execute कराता है। return control 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-else structure तोड़ने पर indented return exceptional situation जैसा दिखने लगता है। इस convention को follow करने पर return statements 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 और सच में पागल कर देने वाला होता है

    • मुझे लगता है कि functions को शायद output type explicit करना चाहिए
      मुख्य वजह यह है कि उस 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 नहीं देता
    • TypeScript में return type का कुछ हिस्सा inferred हो तब भी, बिल्कुल type information न होने वाले JavaScript की तुलना में थोड़ी भी type information होना मुझे पसंद है
      JavaScript में sure नहीं हो सकते, इसलिए stack में ऊपर-नीचे लगातार जाना और उसे दिमाग में बनाए रखना पड़ता है
    • मेरी policy है कि TypeScript compiler जब चिल्लाए तभी type लगाता हूँ
  • “कम variables वाले छोटे functions आम तौर पर पढ़ने में आसान होते हैं” — इस बात को लेकर सावधान रहना चाहिए
    मुझे यह पसंद नहीं कि readability की चर्चा सिर्फ़ micro-readability पर केंद्रित हो जाए। तब macro-readability की तुलना में micro-readability ज़्यादा महत्वपूर्ण है—इस गलत धारणा के तहत code को बहुत ज़्यादा छोटे-छोटे हिस्सों में बाँटना आसान हो जाता है
    इस तरह की कट्टरता ऐसे programmers बनाती है जो जंगल नहीं, सिर्फ़ पेड़ देखते हैं, और इससे बहुत ज़्यादा inefficient code या debug करने में कठिन code पैदा होता है
    APL परिवार की languages दूसरी चरम सीमा पर हैं, लेकिन असली optimum शायद बीच में कहीं है और व्यक्ति के हिसाब से काफ़ी बदल सकता है

    • ख़ासकर जब कई files आपस में उलझी हों, तो बीच का बिंदु साफ़ तौर पर मौजूद होता है
      अपरिचित 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 कर पाना चाहिए

    • सहमत हूँ। compile होने के बाद compiler को शायद इस बात से फ़र्क नहीं पड़ेगा कि return value को नाम दिया गया था
      बेशक, यह मानकर चल रहा हूँ कि 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 हो सकते हैं

    • मुख्य कठिनाइयों में से एक यह है कि हम code पढ़ना सीखते हैं
      आपने क्या पढ़ना और लिखना सीखा है, वही तय करता है कि आपको क्या पढ़ने में आसान लगता है
      आप क्या करने की कोशिश कर रहे हैं, किसके साथ काम कर रहे हैं, coding से पहले क्या कर पाते थे, कौन-सी दूसरी languages जानते हैं—ऐसे कई factors असर डालते हैं
      low-hanging fruit लेने के बाद, जैसे variable names को arbitrary, irrelevant या misleading न रखने के स्तर से आगे जाने पर, कई “readability” समस्याएँ अंततः consensus बनाने की बात हो सकती हैं
      जिन खास programmers के group के साथ आप काम करना चाहते हैं, उससे परे कोई सही जवाब शायद न हो
    • मुझे इसमें खास value नहीं दिखती
      code readability language readability जैसी है; आम तौर पर यह उन लोगों के लिए समस्या होती है जो उस language को अच्छी तरह नहीं जानते, और समय देने पर हल हो जाती है
      programming की असली समस्या code complexity है, और इसका judgement अलग-अलग code snippets के metrics से नहीं किया जा सकता
      समस्या function body के अंदर implementation choices से ज़्यादा functions के बीच relationships में होती है
    • वह approach बहुत one-dimensional है
      code क्या करता है, यह पता लगाना आम तौर पर आसान होता है; मुश्किल काम उस code को modify करना या feature add करना होता है
      यह कठिनाई इसलिए पैदा होती है क्योंकि abstraction की कई layers एक-दूसरे से कैसे जुड़ी हैं, यह छिपा देती हैं