1 पॉइंट द्वारा GN⁺ 2023-11-16 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • दो code structuring rules मुख्य हैं: condition branching को caller की ओर ऊपर खींचना, और repeated processing को batch operations की ओर नीचे धकेलना
  • if को ऊपर ले जाने से preconditions को type या assert से enforce करना आसान हो जाता है, और जटिल control flow को एक जगह इकट्ठा करके duplicate conditions और dead branches ढूँढना आसान होता है
  • enum बनाने के तुरंत बाद फिर से match करने वाली संरचना एक ही condition को branching, data structure, और re-branching में दोहराती है, इसलिए इसे foo(x) और bar(y) कॉल तक सरल बनाने की गुंजाइश होती है
  • for को नीचे ले जाने पर individual object iteration के बजाय batch operations मूल इकाई बन जाते हैं, जिससे startup cost amortization, processing order बदलना, vectorization, और struct-of-array optimization संभव हो जाते हैं
  • condition को loop के बाहर निकालने से hot loop में branching कम की जा सकती है, और यह TigerBeetle जैसी उस संरचना तक भी जुड़ता है जहाँ control plane के decision cost को data plane की batch processing पर amortize किया जाता है

if को ऊपर ले जाना

  • किसी function के अंदर का if condition पहले इस नज़र से देखना चाहिए कि क्या उसे caller की ओर ले जाया जा सकता है
    • उदाहरण के तौर पर, Option<Walrus> लेकर None होने पर return कर देने वाले function की तुलना में सीधे Walrus लेने वाला function बेहतर रूप माना गया है
    • precondition को function के अंदर जाँचकर “कुछ न करना” से बेहतर है कि caller उसे जाँचे और type या assert से उसके सही होने को enforce करे
  • precondition checks को ऊपर ले जाने वाला refactoring call path के साथ propagate हो सकता है, और नतीजतन पूरे सिस्टम में checks की संख्या कम हो सकती है
  • control flow और if जटिलता बढ़ाते हैं और अक्सर bugs का कारण बनते हैं
    • if को ऊपर ले जाने से जटिल branching logic एक function में इकट्ठा हो जाती है, और असली काम सीधे-सादे lower-level routines में चला जाता है
    • जटिल control flow पूरे file में फैला हो, उससे बेहतर है कि वह एक screen में दिखने वाले एक function में हो, क्योंकि तब duplicate और dead conditions ढूँढना आसान होता है
  • dissolving enum” refactoring उस pattern को हटाने का तरीका है जिसमें branch को पहले data structure में बदला जाता है और फिर दोबारा branch किया जाता है
    • अगर f() condition के हिसाब से E::Foo(x) या E::Bar(y) बनाता है, और g(e) फिर match से foo(x) या bar(y) को call करता है, तो वही condition कई रूपों में दोहराई जाती है
    • condition को main() तक ऊपर ले जाने पर इसे if condition { foo(x) } else { bar(y) } जैसे सरल रूप में बदला जा सकता है

for को नीचे ले जाना

  • data-oriented नज़रिए से program और hot path अक्सर बहुत सारे objects को संभालते हैं, इसलिए objects के batch को introduce किया जाता है और batch operations को default case माना जाता है
    • individual object के लिए scalar version, batch operation का special case बन जाता है
    • for walrus in walruses { frobnicate(walrus) } की तुलना में frobnicate_batch(walruses) बेहतर रूप है
  • इसका मुख्य फायदा performance है
    • पूरे batch को एक साथ handle करने पर startup cost को amortize किया जा सकता है
    • processing order को लचीले ढंग से बदला जा सकता है, और vectorization या struct-of-array जैसे तरीके भी संभव हो जाते हैं जहाँ पहले सभी entities के एक field को process किया जाए
    • एक चरम उदाहरण के रूप में Vectorized Interpreters Talk का लिंक दिया गया है
  • एक दिलचस्प उदाहरण के तौर पर FFT-आधारित polynomial multiplication आता है
    • कई points पर polynomial को एक साथ evaluate करना, individual points पर बार-बार evaluate करने से तेज़ हो सकता है
  • if और for वाले नियम साथ में भी लागू किए जा सकते हैं
    • अच्छी संरचना वह है जहाँ if condition के बाहर branch के हिसाब से for walrus in walruses चलाया जाए
    • खराब संरचना वह है जहाँ for loop के अंदर हर बार if condition evaluate किया जाए
    • इससे condition के दोबारा evaluation से बचा जा सकता है, hot loop से branching हटाई जा सकती है, और vectorization संभव हो सकती है
  • यह pattern micro level और macro level दोनों पर लागू होता है
    • TigerBeetle की architecture data plane में object batches को एक साथ process करके control plane के decision cost को amortize करती है
    • for संबंधी सलाह का मुख्य motivation performance है, लेकिन jQuery की तरह collection unit पर काम करने का तरीका expressiveness में भी मदद कर सकता है

1 टिप्पणियां

 
GN⁺ 2023-11-16
Hacker News की रायें
  • पहले मुझे आश्चर्य हुआ कि इस लेख को इतना विरोध क्यों मिल रहा है, लेकिन जब याद आया कि यह डेटा-ओरिएंटेड डिज़ाइन वाली सलाह है, तो बात समझ आई
    इस फ़ोरम के कई लोगों की तरह मैं भी ज़्यादातर काम के लिए web apps बनाता हूँ, और उस संदर्भ में यह सलाह बकवास जैसी लग सकती है
    अगर रोज़मर्रा के काम में आपको instruction cache की चिंता करने की ज़रूरत नहीं पड़ती, तो यह heuristic सही लगता है कि इस सलाह को मोटे तौर पर नज़रअंदाज़ किया जा सकता है
    अगर यह अंदाज़ा लगाना हो कि यह सलाह कब महत्वपूर्ण हो जाती है, तो Mike Acton की “Typical C++ Bullshit” खोजकर देखना अच्छा होगा, और यह लेख उसी बात को ज़्यादा समझने लायक ढंग से व्यवस्थित करता हुआ लगता है
    Casey Muratori की चिंता से मैं काफ़ी सहमत हूँ, लेकिन अधिकतर business software को प्रदर्शन से ज़्यादा बदलाव-योग्यता और correctness, यानी “समय के साथ programming” के लिए optimize करना चाहिए

    • मुझे लगता है कि यह सलाह object-oriented programming पर भी अब भी काफ़ी लागू होती है
      डेवलपर जटिल business logic को class के अंदर छोटे-छोटे private methods में बाँटकर DRY बनाना चाहते हैं, और “if को ऊपर उठाओ” branching logic को कई methods में बिखरने से रोकता है
      “for को नीचे ले जाओ” भी महत्वपूर्ण है। कई call flows अंत में महंगे database queries पर खत्म होते हैं, और अक्सर ऊपर का loop नीचे कई बार DB calls बनाता है
      उस repetition को अक्सर SQL के where clause या join से बदला जा सकता है, और aggregation या filtering को भी ढेर सारे objects निकालकर loop करने के बजाय DAO के करीब धकेलना DB के पास optimize करने के लिए बेहतर होता है
      हालांकि हर design principle की तरह इसे सिद्धांतवादी ढंग से नहीं, बल्कि सोच-समझकर लागू करना चाहिए
    • डेटा-ओरिएंटेड डिज़ाइन हमेशा लोगों को चुभता हुआ लगता है। शायद इसलिए कि यह मौजूदा dominant object-oriented approach के कई हिस्सों को गलत बताने का संकेत देता है
      बदलाव-योग्यता और performance को एक-दूसरे के विरोध में देखना अफ़सोसजनक है। असल में दोनों के टकराने का कोई ठोस सबूत मैंने अभी तक नहीं देखा
    • बदलाव-योग्यता, correctness और performance एक-दूसरे से अलग-थलग नहीं हैं; बल्कि अक्सर साथ-साथ चलते हैं
      performance के सबसे महत्वपूर्ण पहलुओं में से एक है चीज़ों को छोटा बनाना। छोटा code, छोटे data structures, और executed instructions की कम संख्या ही मूल बात है, और “instruction cache के बारे में सोचना” भी मूल रूप से छोटा code लिखना ही है
      code जितना छोटा होगा, bugs घुसने की जगह उतनी कम होगी, उसे एक बार में समझना आसान होगा, और test coverage पाना भी बेहतर होगा, इसलिए यह correctness के पक्ष में है। बदलाव-योग्यता के लिए भी code जितना छोटा होगा, बदलाव उतना छोटा होगा
      बेशक parallelization, caching, low-level optimization जैसे optimizations code को जटिल बना सकते हैं, लेकिन ये performance optimization का सिर्फ़ एक हिस्सा हैं, और performance को गंभीरता से लेने वाला programmer profiling और analysis के बिना ऐसे काम नहीं करता
      if/for और for/if functionality के लिहाज़ से बहुत अलग न हों, फिर भी एक तेज़ हो सकता है; इसलिए अगर बिना लागत के सही वाला इस्तेमाल किया जा सकता है, तो धीमा विकल्प चुनने की वजह नहीं है
    • John Ousterhout की बात सुनें तो, व्यवहार में बदलाव-योग्यता·correctness उतना performance से नहीं टकराते जितना पहले लगता है
      सरल programs में call stack छोटा होता है और वे जटिल व महंगे abstractions से बचते हैं, इसलिए वे कम memory इस्तेमाल करते हैं और तेज़ चलने की प्रवृत्ति रखते हैं
      सर्वोच्च स्तर की performance के पीछे जाने पर program जटिल हो सकता है, लेकिन असली simplicity ज़्यादातर reasonable performance भी देती है
      “for को नीचे ले जाना” मुख्यतः data-oriented सलाह है, लेकिन “if को ऊपर उठाना” program को ज़्यादा सरल बनाने के करीब है। ठीक-ठीक कहें तो यह source code की locality बढ़ाने की बात है, और branching logic को एक जगह इकट्ठा करना इसका मूल है: https://loup-vaillant.fr/articles/source-of-readability
    • business apps पर भी ये दोनों सलाहें काफ़ी लागू होती हैं, ऐसा मुझे लगता है
      खासकर “loop को नीचे ले जाओ” CRUD apps में शक्तिशाली है। जहाँ तक हो सके creation और updates को batch processing में करने से अक्सर CPU bottleneck से कहीं ज़्यादा समय बचता है
      items.map(insertToDb/postToServer) और insertToDb/postToServer(items) के बीच का अंतर लगभग हर मामले में कई orders of magnitude का होता है
      मैंने देखा है कि ऐसे optimization से काम का समय seconds/minutes से milliseconds तक घट जाता है, और अक्सर API भी ज़्यादा साफ़ हो जाती है और logs पढ़ना भी आसान हो जाता है
  • अनुभव बढ़ने के साथ मुझे लगता है कि बहुत ज़्यादा programmers छोटी-छोटी इकाइयों के “सुंदर code” पर तो ध्यान देते हैं, लेकिन पूरे codebase design पर पर्याप्त ध्यान नहीं देते
    संक्षिप्त और अच्छी तरह तराशे हुए functions अच्छी बात हैं, लेकिन ऐसे लेख PR या discussions में बेकार के bikeshedding में बदल सकते हैं
    function थोड़ा messy है या नहीं, if और for कहाँ रखे गए हैं, map और filter इस्तेमाल हुए हैं या नहीं—अगर function का नाम ठीक है, interfaces और types expressive हैं, उद्देश्य साफ है, documentation है, और side effects का जरूरत से ज़्यादा इस्तेमाल नहीं है, तो ये बातें मुझे बहुत मायने नहीं रखतीं

    • “if को ऊपर ले जाओ” कोई bikeshedding नहीं है; बल्कि जैसा आपने कहा, यह तय किए जाने वाले architectural choice के ज्यादा करीब है
      अगर सोचें कि input मिलने पर validation कहाँ करना है, तो इस heuristic के हिसाब से input लेने वाली सबसे ऊपरी जगह पर validate करना चाहिए
      यह code समझने में भी मदद करता है, और proof के नजरिए से भी preconditions को ऊपर propagate करना चाहिए, इसलिए तकनीकी रूप से भी जरूरी है
      पहली सलाह निश्चित रूप से कोई मामूली बहस नहीं है, और दूसरी थोड़ी ज्यादा धुंधली है
    • Security में काम करते हुए FAANG मानकों के हिसाब से L7~L9 के कई tech leads और architects से सामना हुआ, और “सही design” को लेकर सबसे ज्यादा जुनूनी लोग आम तौर पर 5~10 साल के अनुभव वाले होते थे
      वे juniors को अपने ही पैर पर कुल्हाड़ी मारने से बचाना चाहते हैं, लेकिन complexity की cost और code लंबे समय में कैसे बदलता है, इसकी समझ अक्सर अभी पर्याप्त नहीं होती
      उल्टा, 20 साल से ज्यादा करने के बाद ज्यादातर technical तत्वों की तुलना में simplicity को ज्यादा महत्व देने लगते हैं
      “branch कितनी जल्दी करें”, “इस code को असल में करना क्या है” जैसे सवाल लंबे समय में सबसे मूल्यवान जवाब देते हैं
      Abstraction और encapsulation से जुड़े सवाल उल्टे ऊपर बताए गए तरह की बहसों की ओर आसानी से ले जाते हैं, और “big picture” ही देखने वालों के code में security issues सबसे ज्यादा इसलिए भी निकलते हैं क्योंकि वे वास्तव में उस well-designed codebase के काम को ठीक से नहीं समझते
    • अगर ऐसे मामूली दिखने वाले principles पहले से तय हों, तो PR और discussions में उसी तरह की थकाऊ बहसें कम हो सकती हैं
      Data-oriented design के लिए अच्छी तरह फिट होने वाले performance-sensitive software में ऐसी छोटी इकाइयों वाली चीजों पर ज्यादा ध्यान देना चाहिए। Compiler optimization इसी तरह काम करती है
      तब game के rules बदल जाते हैं। Statements का अर्थ महत्वपूर्ण हो जाता है, self-explanatory code ज्यादा मायने रखने लगता है, code comments सिर्फ reasoning के लिए रह जाते हैं, और “documentation” specification और user manual के ज्यादा करीब हो जाती है
    • इसका उल्टा भी बहुत देखा है। Over-engineered cathedral design करना पसंद करने वाले, लेकिन algorithm की low-level efficiency के बारे में न सोचने वाले लोग भी बहुत हैं
    • एक वजह यह है कि programming languages में functions से कहीं बड़े components, या functions से अलग तरीके से जुड़ने वाले components को meaningful तरीके से express करने का तरीका कम है
      ज्यादा से ज्यादा आप functions को group कर सकते हैं, और उसके बाद सब खुद संभालना पड़ता है
  • “if को ऊपर ले जाना” का नुकसान यह है कि function definition के अंदर preconditions और postconditions सीधे दिखाई नहीं देते, और हर call site पर check करना पड़ता है
    कई लोगों वाले बड़े projects में ऐसा function अपने intended context के बाहर reuse होकर bugs पैदा कर सकता है
    Contract framework इस्तेमाल करें तो हल हो सकता है, लेकिन condition को contract और code में दो बार लिखना पड़ता है, और dependent types के साथ भी यही है
    किसी खास context से जुड़े code region को tag करना, और उस context के अंदर ही call किए जा सकने वाले functions define करना एक दिलचस्प तरीका है
    Python में @requires_context("VALIDATED_XY") जैसे decorator और validated_xy context manager से function को सिर्फ validated region में call करवाया जा सकता है
    Runtime उस context का meaning नहीं जानता, लेकिन tools और tests के साथ इसे इस तरह design किया जा सकता है कि desired context सिर्फ तब बने जब कोई खास condition satisfy हो
    Haskell जैसी languages में identity monad जैसी चीज से type level पर enforce किया जा सकता है, और type-level enforcement न भी हो तो भी “unsafe” code regions को protect करने का यह दिलचस्प तरीका हो सकता है

    • लगता है आपने लेखक के तर्क का दूसरा हिस्सा मिस कर दिया। बात यह है कि “precondition check को caller तक push किया जा सकता है और types से enforce किया जा सकता है”
      इसलिए precondition अभी भी function definition में सीधे दिखती है, बस if statement नहीं बल्कि type signature के हिस्से के रूप में
      यह लेख में इस्तेमाल Rust में common pattern है, और if check के उलट runtime नहीं बल्कि compile time पर check होने वाली strict precondition है; satisfy न होने पर program compile ही नहीं होता
    • Python में भी types से इसे express किया जा सकता है
      जैसे do_something(position: ValidatedPosition) के रूप में लेना, और सामान्य Position को validate करने के बाद ValidatedPosition में बदलकर pass करना
      असल में validation को ValidatedPosition constructor के अंदर encapsulate करेंगे, लेकिन मुख्य बात यह है कि अगर Position को सीधे pass करने की कोशिश करेंगे तो mypy बताएगा कि गलत type pass किया गया है
      Python की type checking Rust जितनी comprehensive नहीं है, लेकिन जब यह guarantee चाहिए कि pass किया गया data ठीक से process हुआ है, तब यह धीरे-धीरे उपयोगी होता जा रहा है
    • “if को ऊपर ले जाओ” वाली सलाह precondition checks के बारे में नहीं, बल्कि सही code path चुनने के बारे में है
      अगर किसी function की precondition है, तो स्वाभाविक रूप से function की शुरुआत में assert किया जा सकता है। उदाहरण के लिए Java type system null allow करता है, इसलिए जिस function को object चाहिए वह null आने पर exception throw करे
      हर call site की जिम्मेदारी है कि precondition true होने पर ही function call करे। precondition की परिभाषा से यह स्वाभाविक है
      Precondition तोड़कर function call करना caller का bug है। Function के अंदर इसे check करके undefined behavior रोकने वाला code जरूरी हो सकता है, लेकिन ऐसे assertions और program के actual control flow में फर्क करना चाहिए, और लेख बाद वाली बात पर है
    • इस example के fn frobnicate(walrus: Walrus) में, owned Walrus न होने वाली चीज pass करें तो program compile नहीं होगा
      Generic होने पर भी argument को trait bounds satisfy करने पड़ते हैं, और function के अंदर argument कैसे use किया जाता है, उसके आधार पर compiler function definition के लिए जरूरी bounds मांगता है
    • मुझे लगता है public और private का intention कुछ हद तक code को किसी खास context के हिसाब से tag करने का ही रहा होगा
      या फिर public, private, और .NET ecosystem के protected जिन क्षेत्रों को handle करते हैं, उनसे पार जाने वाली ज्यादा specific semantics की जरूरत हो सकती है
  • पहला उदाहरण if और for की वजह से नहीं, बल्कि किसी और वजह से अच्छा नहीं है
    आम तौर पर अगर किसी चीज़ का “container” है, तो function उस container पर नहीं, बल्कि उसके अंदर मौजूद domain-level “object” पर लिखना बेहतर होता है
    Clojure में agent इस्तेमाल करते समय agent के लिए functions नहीं लिखते, बल्कि उस object के लिए functions लिखते हैं जिसे agent hold कर सकता है
    Elixir में भी core domain functions PID पर नहीं, बल्कि अंदर की domain data structure पर काम करते हैं, और ज़रूरत पड़ने पर GenServer call उसी को delegate कर दी जाती है
    इससे ज़्यादा flexibility मिलती है, और core domain concern “Walrus को frobnicate करना” तथा application concern “Walrus हो भी सकता है और नहीं भी; अगर है तो frobnicate करना” को ज़्यादा साफ़ तरीके से अलग किया जा सकता है

    • दी गई सलाह में validation logic को बहुत ऊपर उठा देने की संभावना दिखती है
      शुरुआत में validation करना अच्छा है, लेकिन यह भी ज़रूरी है कि function अजीब error के साथ अचानक फटने के बजाय साफ़ तौर पर validation error दे
      Haskell इस समस्या को newtype से हल करता है, यानी “एक transparent container जो certify करता है कि proper validation पहले ही हो चुकी है”
      जो सलाह मैं सच में लोगों पर ज़ोर देकर कहना चाहूँगा, वह है “sad if” को प्राथमिकता दें। normal path को कई layers में nest करने की तुलना में, गलत conditions को ऊपर से एक-एक करके check करना और तुरंत ठीक करना या stop करना लगभग हमेशा ज़्यादा readable और maintainable होता है
      इंसान स्वभाव से expected case पर focus करना चाहता है, लेकिन code इसके उलट exceptional cases पर पहले focus चाहता है। हर if दिमाग़ पर बोझ बनाता है, और अगर external system से information लानी हो या error के कारण early exit करना हो, तो detection और handling साथ-साथ होने पर वह बोझ तुरंत उतर जाता है
    • अगर मतलब यह है कि पहला उदाहरण walrus.frobnicate() होता तो बेहतर होता, तो मुझे लगता है यह लेखक के core point से ज़्यादा syntax preference का मामला है
  • मैं यह नहीं कहूँगा कि यह खराब सलाह है, लेकिन यह ज़रूरी नहीं कि अच्छी सलाह भी हो
    चुनी गई language Rust है, यह बात काफी कुछ कहती है। strong type system दूसरी languages में ज़रूरी बहुत सारी defensive programming को रोक देता है
    अगर कोई C programmer function में pass किए गए pointer की validity check न करके NULL dereference कर देता है, तो मैं ऐसे व्यक्ति के साथ team में काम नहीं करना चाहूँगा
    इसलिए कम से कम कुछ if निश्चित रूप से नीचे की तरफ होने चाहिए, और अच्छा हो कि errors ठीक से ऊपर propagate हों
    for के बारे में मेरी राय इतनी strong नहीं है, लेकिन C में array argument pointer में decay हो जाता है, इसलिए मुझे लगता है कि iteration भी नीचे नहीं बल्कि ऊपर होनी चाहिए। जिस function में array बनाया गया है, वहाँ length पर भरोसा किया जा सकता है, लेकिन जिस function को argument के रूप में pass किया गया है, वहाँ नहीं

    • C के interfaces में expectations को सावधानी से document करना चाहिए, और बस उतनी ही checking करनी चाहिए
      documentation को strong type system की जगह लेनी चाहिए, runtime checks को नहीं
      NULL checks और अन्य defensive measures से भरा code कहीं ज़्यादा पढ़ने में कठिन होता है
      library boundary पर ज़्यादा defensive checks की ज़रूरत कही जा सकती है, और यह लेख ठीक ऐसे ही checks को ऊपर धकेलने की बात करता है
      security-critical code अलग हो सकता है, लेकिन ज़्यादातर मामलों में accidental NULL dereference tests, sanitizers और fuzzing से पकड़ा जाए तो काफी है
    • C में NULL अक्सर एक valid लेकिन dereference न किया जा सकने वाला pointer value होता है
      invalid pointer value check करने के लिए आपको संभावित अनगिनत गलत values का पूरा set check करना होगा, और अगर आप सिर्फ NULL check करते हैं तो असल में invalid value check नहीं कर रहे होते
      अगर function की precondition है “parameter p NULL नहीं हो सकता”, तो check किया जा सकता है। लेकिन अगर precondition है “p valid pointer होना चाहिए”, तो सही assertion condition ढूँढने के लिए शुभकामनाएँ
    • मेरा heuristic यह है कि अगर type system गलत values को रोक नहीं पाता, तो runtime पर उन्हें रोकने की जिम्मेदारी होती है
      आजकल मैं T-SQL काफी इस्तेमाल करता हूँ, और parameters या variables को NOT NULL declare नहीं किया जा सकता
      इसलिए जितना reasonably possible हो उतना early, आम तौर पर stored procedure के बिल्कुल ऊपर, NULL check करना अच्छा है
      वरना NULL call hierarchy में गहराई तक अनपेक्षित रूप से propagate होकर कम स्पष्ट problems पैदा कर सकता है
      अच्छी बात है कि table data को NOT NULL declare किया जा सकता है, इसलिए ऐसे bugs आम तौर पर data खराब नहीं करते, लेकिन उन्हें जल्दी पकड़ना कहीं ज़्यादा आसान है
      हालांकि अगर किसी parameter value के आधार पर DB में लिखने वाली logic हो और वह value अनपेक्षित रूप से NULL हो, तो गलत value लिखी जा सकती है या ज़रूरी value बिल्कुल नहीं लिखी जा सकती, जिससे असल में data corrupt हो सकता है
      इसलिए defensive programming ही जवाब है
  • उचित context के बिना देखें तो यह सलाह काफी अजीब है, और शायद खराब सलाह भी हो सकती है
    loops और if statements दोनों ही control flow operations हैं, इसलिए लेख के कुछ arguments बहुत समझ में नहीं आते
    सबसे मजबूत argument performance लगता है, लेकिन performance आम तौर पर सबसे आखिर में चिंता करने वाली चीज़ होनी चाहिए, खासकर जब बात heuristic advice की हो
    अफ़सोस है कि लेखक ने इसे catchphrase बना दिया, और मैं चाहूँगा कि यह न फैले

    • performance argument हो सकता है, लेकिन strong argument नहीं
      लेखक के example की तरह अगर improved code लिखा जा सकता है, तो condition loop execution के दौरान constant है। अगर हर बार कोई महंगी condition evaluate नहीं हो रही, तो branch prediction ज़्यादातर काम संभाल लेगा
      अगर condition const values से बना boolean expression है, तो compiler के भी इसे समझ लेने की संभावना है
  • मुझे लगा था कि पूरा लेख ऐसे examples तक जाएगा: condition के आधार पर walrus को बाँटकर frobnicate_batch(fwalrus) और transmogrify_batch(twalrus) call करने का तरीका
    लेकिन असल में वह एक condition से पूरी loop को दो branches में wrap करने वाले example पर चला गया

  • software engineering background से आए programmers को यह अक्सर गलत करते देखना हैरान करता है
    मैंने scientific field में programming शुरू की थी, और वहाँ ऐसी चीज़ों के बारे में सोचना बिल्कुल आवश्यक है
    loop order गलत रखने भर से simulation 1 घंटे की जगह 1 हफ्ता ले सकता है
    उस background की वजह से for और if का order सही रखकर छोटी optimizations करना instinctive हो गया है, और जो code ऐसा नहीं करता वह बस गलत लगता है

  • मुझे यकीन नहीं है कि ऐसे सामान्य नियम असल code पर सच में लागू किए जा सकते हैं या नहीं
    ऐसे नियम अक्सर गलत जगह रखे गए सिद्धांत जैसे लगते हैं, और भले ही कोई blog post खुद को heuristic कहकर शुरू करे, युवा programmers उसे हमेशा उसी तरह नहीं लेते
    कुछ हफ्ते पहले YouTube मुझे “I'm a never-nester” नाम का video लगातार recommend कर रहा था, जो शायद यह दावा कर रहा था कि if को कभी nest नहीं करना चाहिए, और यह काफी हास्यास्पद था
    लेख के उदाहरण की तरह for के अंदर if condition वाले code को “बुरा” बताया गया था, लेकिन असल में इस तरह लिखे गए code के ज़्यादातर मामलों में condition walrus पर निर्भर होती है, इसलिए if को ऊपर नहीं ले जाया जा सकता
    अगर ऊपर ले जाना संभव हो, तो वही condition हर loop में दोबारा evaluate करना इतना साफ़ तौर पर बेकार दिखता है कि programmers स्वाभाविक रूप से इससे बचते हैं
    लेकिन अगर कोई junior या student किसी सिद्धांत जैसा सुनाई देने वाला सुझाव पढ़ता है, तो वह उसे सख्ती से मानने की कोशिश में और खराब code बना सकता है

    • “कभी nest न करें” को लेकर मैं खास तौर पर dogmatic नहीं हूं, लेकिन syntax संभव हो तो match (condition_a, condition_b) से चार cases अलग करना nested if से बेहतर न हो—ऐसा मैंने अनुभव में कभी नहीं देखा
    • मुझे यह बल्कि if को “ऊपर ले जाने” का अच्छा उदाहरण लगता है
      code का मकसद condition के आधार पर walrus पर कोई खास operation करना है, लेकिन सच में if polymorphism और type system की जगह इस्तेमाल हो रहा है
      walrus में अलग-अलग scenarios में call किए जाने वाले दो functions क्यों होने चाहिए? एक function और दो types रखकर सही type pass क्यों न किया जाए?
      मौजूदा structure में भी ऊपर की तरफ condition के आधार पर processing function चुना जा सकता है, और नीचे वाले loop में चुने हुए function को हर बार call करवाया जा सकता है
      फैसला जितना जल्दी हो सके कर लिया जाए, तो उसे पूरे code में बिखेरने की जरूरत नहीं रहती। code का अंदरूनी हिस्सा बिना branch के हर बार वही काम करता है, और output सिर्फ configuration graph के जरिए बदलता है
      बेशक यह नया विचार नहीं है; 15 साल पहले भी यह पहले से पुराना idea था: https://www.youtube.com/watch?v=4F72VULWFvc
    • “GOOD” refactoring तभी काम करती है जब condition walrus पर निर्भर न हो, और यह बात स्पष्ट रूप से दिखाने में मदद करती है
      यहां “for को नीचे ले जाओ” फिर से लागू करने पर if condition के अंदर frobnicate_batch(walruses) या transmogrify_batch(walruses) call करने जैसा रूप बनता है
    • मुझे लगता है ऐसे लेखों का होना अच्छा है
      जिस समस्या से मैं कई बार टकराया था लेकिन ठीक से बयान नहीं कर पाया था, उसे किसी ने शब्दों में व्यवस्थित कर दिया, और यह दिमाग में रखने लायक अच्छा model लगता है
      साथ ही ऐसी आलोचना भी मूल्यवान है। उम्मीद है कि कोई dogmatic junior developer इसे साथ में पढ़े और थोड़ा ज्यादा सूक्ष्म तरीके से निर्णय ले
    • सभी “heuristics” और “best practices” से सीखने वाली मुख्य बात उनके पीछे की वजह है
      programmers को खासकर ऐसी चीजों को copy करके यांत्रिक रूप से लागू नहीं करना चाहिए
      heuristics और best practices को आंख मूंदकर लागू करना अच्छा विचार नहीं हो सकता, और आखिरकार सब कुछ “संदर्भ पर निर्भर” होता है
  • ऐसे heuristics में आम तौर पर ज्ञान की थोड़ी-सी धूल होती है, लेकिन ज्यादातर वे नई समस्या पैदा करते हैं जिसे नए programmers से फिर से de-dogmatize करना पड़ता है
    जब कोई उन्हें बहुत सख्ती से मानने की कोशिश करता है तो चीजें खराब होने के मामले हमेशा बहुत होते हैं, और “यह जानो कि इस सलाह को कब नहीं मानना है” ही असल में मुख्य कठिनाई है

    • de-dogmatization तो वैसे भी जरूरी है
      मुझे लगता है ये rules आज़माने के लिए उपयोगी हैं। इन्हें अपनाकर देखो, पूरी सीमा तक धकेलकर देखो, एक दिन या एक साल बाद उलटकर भी देखो, और देखो कि ये कहां तक जाते हैं
      सीमाएं सीखना भी लगातार फिर से लिखे जा रहे parchment पर जुड़ने वाली एक और परत है
    • यह लेख किसी बड़े संग्रह के अंदर एक koan के रूप में उपयोगी हो सकता है
      उन koans में से कुछ को एक-दूसरे से विरोधाभासी होना चाहिए