3 पॉइंट द्वारा GN⁺ 2023-09-17 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • छोटी functions में बाँटकर abstraction level अलग करने के तरीके की तुलना में, ऊपर से नीचे तक बहने वाला रैखिक कोड पूरे flow को समझना आसान बनाता है
  • function extraction से top-down structure बनाया जाए तो bake और bakePizza जैसे मिलते-जुलते नाम वाली functions के बीच आना-जाना कर जाँच करनी पड़ सकती है
  • oven preheat की जगह या pizza को दो बार पलटने पर क्या होगा जैसे मामलों में, छोटी functions इरादा दिखाने के बजाय असल behavior छिपा सकती हैं
  • रैखिक कोड में चरणवार comments जोड़ने से indirect references बढ़ाए बिना काम का इरादा समझाया जा सकता है, इसलिए यह अतिरिक्त abstraction से अधिक पढ़ने योग्य हो सकता है
  • सिर्फ एक बार इस्तेमाल होने वाली छोटी function निकालने पर रैखिकता की हानि होती है, और उदाहरण में oven बनाने के तरीके की तरह वास्तविक code में performance समस्या भी सामने आ सकती है

function extraction से अधिक महत्वपूर्ण होने वाले मामले: रैखिकता

  • Google Testing Blog के उदाहरण में createPizza की दो implementations की तुलना की गई है, और माना गया है कि दाईं ओर की implementation abstraction level को नहीं मिलाती, इसलिए वह अधिक पढ़ने योग्य और top-down है
  • इसके विपरीत मत यह है कि बाईं ओर की implementation स्क्रीन पर ऊपर से नीचे तक रैखिक रूप से पढ़ी जाने वाली code होने के कारण अधिक महत्वपूर्ण है
    • दाईं ओर की implementation में पूरे behavior को समझने के लिए कई छोटी function definitions तक जाना पड़ता है
    • प्रस्तुति में भी दाईं ओर के code का कुछ हिस्सा छोड़ा गया है, इसलिए दोनों implementations का आकार समान दिखता है, लेकिन वास्तव में दाईं ओर वाला अधिक लंबा है
  • function extraction की वजह से सिर्फ नाम देखकर behavior को पर्याप्त रूप से समझना कठिन हो सकता है
    • bake और bakePizza दोनों मौजूद हों तो कौन-सी function oven गरम करती है, यह तुरंत समझना आसान नहीं होता
    • एक ही pizza को दो बार पलटने पर वह idempotent है या परिणाम खराब हो जाएगा, यह जानने के लिए अंदर की implementation देखनी पड़ती है

comments वाले रैखिक code और oven उदाहरण

  • बाईं ओर के रैखिक code में दाईं ओर की function names को comments के रूप में जोड़ दिया जाए, तो उसे सबसे पढ़ने योग्य रूप माना गया है
    • Prepare pizza, Add toppings, Heat oven, Bake pizza, Box and slice जैसे comments हर चरण के इरादे को दिखाते हैं
    • readability अतिरिक्त abstraction layers और indirect references से नहीं, बल्कि इस बात से आती है कि इस समय जो काम हो रहा है उसे ठीक से समझाया जाए
  • निष्कर्ष यह है कि सिर्फ एक बार इस्तेमाल होने वाली छोटी functions को रैखिक code से अलग निकालना नहीं चाहिए
    • छोटी function extraction का लाभ रैखिकता की हानि की भरपाई नहीं करता
  • उदाहरण में oven handling भी संरचनात्मक रूप से असहज लगती है
    • oven preheat अपने आप में एक पूर्ण क्रिया है, इसलिए उसे oven की method होना अधिक उपयुक्त है
    • हर pizza बनाने पर नया oven बनाना और उसे preheat करना वास्तविक उपयोग पैटर्न से मेल नहीं खाता
    • वास्तविक code में भी ऐसा structure दिखाई देता है, और कभी-कभी यह performance समस्या पैदा कर सकता है
  • createPizza के अंदर नया oven बनाने के बजाय, संभव है कि oven को parameter के रूप में लिया जाना चाहिए
    • oven उपलब्ध कराना caller की जिम्मेदारी के अधिक करीब है
    • अगर flow pizza को box में रखने का है, तो pizza नहीं बल्कि box लौटाने वाला interface अधिक स्वाभाविक हो सकता है

1 टिप्पणियां

 
GN⁺ 2023-09-17
Hacker News की राय
  • यह style का मामला है; खाना पकाने की तरह, नमक बहुत ज़्यादा हो या बहुत कम, दोनों ही डिश बिगाड़ देते हैं
    उम्मीद है कि यहाँ कोई 1000 लाइनों वाला god function सुझा नहीं रहा है, और हर function अधिकतम 5 लाइनों का हो, यह भी पढ़ने में अच्छा नहीं होता। कहाँ बाँटना है, इसके लिए निर्णय, अच्छी समझ और iteration चाहिए। पहली कोशिश में बनाई गई abstraction खराब निकली, इसलिए abstraction छोड़ देने की ज़रूरत नहीं; कुछ बार refactor करने पर business domain से अच्छी तरह मेल खाने वाली classes और APIs मिल सकती हैं
    साथ ही abstraction को लेकर बहुत जल्दबाज़ी नहीं करनी चाहिए, या कुछ लाइनों की duplication देखकर ऐसे बर्ताव नहीं करना चाहिए जैसे कोई घातक चोट लग गई हो। जल्दबाज़ी में की गई abstraction अक्सर ऐसे code को बाँध देती है जिसे साथ-साथ evolve करने की ज़रूरत नहीं होती। किसी task unit को छिपाने के लिए ऐसा function निकालना जिसे सिर्फ एक जगह call किया जाता है, algorithm को साफ बना सकता है, खासकर boilerplate या business logic और DB connection handling जैसे infrastructure concerns के मिश्रण को छिपाने में यह उपयोगी होता है। लेकिन इसका इस्तेमाल सावधानी से करना चाहिए, और उन steps को तोड़ने से बचना बेहतर है जो abstraction के समान स्तर पर होने चाहिए

    • मुख्य बात यही है। शुरुआती developers बड़े functions लिखने की प्रवृत्ति रखते हैं, और Clean Code जैसी किताब पहली बार पढ़ने वाले उत्साही developers हर चीज़ को कुछ लाइनों वाले लाखों functions में तोड़ना चाहते हैं
      मेरे साथ काम कर चुके एक व्यक्ति ने “पढ़ने में आसान है” के नाम पर हर boolean condition को function में निकाल दिया, और “comments खराब होते हैं” के नाम पर बिल्कुल comments नहीं लिखे। मुझे वह किताब इसलिए पसंद नहीं, क्योंकि वह ऐसी खराब सलाह को आँख मूँदकर मानने वाले कट्टर समर्थक बना देती है
    • 1000 लाइनों वाला god function क्यों नहीं हो सकता? किसने कहा कि वह ज़्यादा खराब है, और कौन-सी study ने यह निष्कर्ष निकाला है
      कभी-कभी domain को 1000 लाइनों वाले god function की ज़रूरत हो सकती है, और logic और काम एक जगह इकट्ठे होने से वह 50 लाइनों वाले 20 functions से कहीं ज़्यादा readable हो सकता है। वैसे भी पूरी चीज़ समझने के लिए वे 20 functions सभी पढ़ने ही पड़ेंगे, और कोई उनमें से कुछ को reuse करने की कोशिश करेगा, फिर मूल काम में न रही 2–3 requirements के हिसाब से उन्हें adjust करेगा, और किसी specific logic को unrelated use case के साथ बाँध सकता है
      अगर वह function pure function है, तो 1000 lines हो या 10000 lines, मुझे फर्क नहीं पड़ता; वह फिर भी ठीक है
    • खाना पकाने वाली analogy में कहें तो, अगर किसी को meal बनाने का तरीका समझाते समय किसी point पर fond डालना हो, तो fond बनाने का तरीका अलग section में समझाना वाजिब है। fond स्वतंत्र चीज़ है और खाने से उसका touch point सिर्फ एक है, इसलिए उसे बाहर निकालना ठीक है और बल्कि फायदेमंद हो सकता है
      cooking recipes भी पहले से ही काफी abstracted होती हैं। “प्याज़ को हल्का sauté करें” कहने का मतलब है कि आपको प्याज़ काटना और हल्का sauté करने का algorithm पहले से पता है। अगर सब कुछ inline लिख दिया जाए तो पढ़ा ही नहीं जा सकेगा
      code भी ऐसा ही है। abstraction को सख्ती से बाहर कर दें तो आप भाषा द्वारा allowed सबसे निचले level तक उतर जाते हैं, और वह निश्चित रूप से readable code नहीं होता। उदाहरण के लिए, Python के decode method के बजाय Unicode decoding खुद करने की कोशिश करें, तो program वास्तव में क्या कर रहा है यह समझना बहुत मुश्किल हो जाएगा। भाषा simple और well-tested abstraction देती है, इसलिए कोई ऐसा नहीं करता; फिर अपने द्वारा simple और well-tested abstraction बनाकर उसे business logic में इस्तेमाल करने से यह अलग कैसे है
      मुश्किल हिस्सा ऐसी abstraction बनाना है जो इतनी अच्छी चुनी गई हो कि किसी को उसे फिर छूने की ज़रूरत न पड़े
    • company team को मैं अक्सर यह feedback देता हूँ कि एक कदम पीछे हटकर बड़े problem domain को देखें, और सोचें कि ये चीज़ें अनिवार्य रूप से एक जैसी हैं या संयोग से एक जैसी हैं
      अभी code lines मिलती-जुलती दिख रही हैं, इसका मतलब यह नहीं कि आगे भी उन्हें एक जैसा होना चाहिए या एक जैसा बनाए रखना चाहिए। “code लगभग repeat हो रहा है” इस वजह से दो अलग-अलग use cases को जबरन मिलाएँ, तो समय के साथ यह ऐसी abstraction बन जाती है जो किसी चीज़ को abstract नहीं करती
      अगर use cases बहुत अलग हो जाएँ, तो implementation बहुत सारा logic caller की तरफ धकेल देती है, या अंतर को flags के रूप में expose करके अंदर दो अलग-अलग implementations को साथ-साथ रख देती है। पहला shallow abstraction है इसलिए उसका value कम है, और दूसरा स्वतंत्र implementations की तुलना में कम स्पष्ट है
    • अच्छी तरह structured 1000-line function हो, तो मैं उसे छोटे functions की सैकड़ों खराब spaghetti से कभी भी बेहतर मानूँगा
  • उदाहरण कोड इतना सरल है कि स्वाभाविक रूप से linear code ज़्यादा पढ़ने योग्य लगता है, लेकिन वह idea अच्छी तरह scale नहीं होता
    reusability या unit testability पर भी विचार करना चाहिए, और अगर सारा code एक ही function में डाल दें, तो पढ़े जा रहे code block से संबंधित हो भी सकने वाले और नहीं भी सकने वाले सभी local variables scope में रहते हैं, जिससे reasoning और कठिन हो सकती है
    हालांकि कम अनुभव वाले दिनों को याद करूँ तो, कई बार ठीक-ठाक linear code को ज़रूरत से ज़्यादा modularize करके ऐसा code बना दिया था जिसमें इधर-उधर कूदना पड़ता था और जो कम maintainable था। शुरुआती रूप उस समय दिमाग में चल रहे सोच के flow के ज़्यादा करीब होता है, और पाठक भी शायद उसी तरह समझेगा—यह इसका फायदा है। अत्यधिक refactoring करने पर यह बात खो सकती है
    आखिरकार programming एक craft के ज़्यादा करीब है, इसलिए परिस्थिति के हिसाब से चुनाव करने में अनुभव मदद करता है

    • काम पर जिन functions को सबसे अच्छी review मिली थी, उनमें से एक 2000-line monster था, और वह 9 अलग-अलग variable scopes को stages की तरह रखने वाली linear style में था
      उसका मकसद सिर्फ एक था। app के एक कोने में, एक platform पर इस्तेमाल होने वाले अलग-अलग HTML pages को दूसरे platform के native feel की नकल करने वाले carousel में बदलना था, और वह उस platform और app के उस हिस्से के लिए बेहद specialized था
      9 scopes में से हर एक को function बनाया जा सकता था, लेकिन तब developers उन्हें reuse करना चाहेंगे। हर stage में पिछले stage में हुई चीज़ों के बारे में सूक्ष्म assumptions थे, और अलग function बनाने के लिए उन assumptions को फिर से जांचना, generalize करना और verify करना पड़ता कि हर method स्वतंत्र रूप से काम करता है। ऐसे code पर, जिसकी कहीं और शायद ही ज़रूरत हो, इतना खर्च करने की वजह नहीं थी
      debugging भी ज़्यादा कठिन नहीं थी, end-to-end tests भी थे, और intermediate stage state function के बाहर leak नहीं होती थी। वास्तव में, समय के साथ 2 अन्य developers ने इसमें modifications में योगदान दिया और यह अच्छी तरह चला, और इसे लिखना भी तेज़ था
      linear code अच्छी तरह scale करता है और problem solve करता है। यह हमेशा desired form नहीं होता, लेकिन अपेक्षा से ज़्यादा situations में जीवन को काफी आसान बना देता है
      पहली बार 2000-line monster देखकर प्रतिक्रिया अच्छी नहीं थी, लेकिन 5 मिनट देखने पर वास्तविक defects ढूँढना मुश्किल था, और कुछ tests होते तो डर सिर्फ ऐसे थे जो वास्तविकता नहीं बनते
    • function में तोड़े गए code के scale होने का सबूत कहाँ है? कुल code complexity बढ़ने पर दर्जनों functions में कटकर unreadable हालत बनने की संभावना भी साथ-साथ बढ़ती है
      किसी बिंदु पर एहसास होता है कि उन दर्जनों functions को एक खास order में ही call किया जाना चाहिए, और हर एक सिर्फ एक बार इस्तेमाल होता है। आखिरकार इसका मतलब है कि अगर कोई उन functions को उपयोगी ढंग से इस्तेमाल करना चाहे, तो उसे किसी जादुई composition order को जानने के लिए मजबूर किया जा रहा है
    • “idea scale नहीं होता” कहना गलत है, और “programming एक craft है, इसलिए experience situation judgment में मदद करता है” कहना सही है
      विशाल linear function अक्सर ज़्यादा readable और desirable होने का मुख्य कारण यह है कि कई concepts और relationships को context switch किए बिना एक ही chunk में साथ-साथ रखकर समझना आसान हो जाता है। इसके एक चरम समर्थक K भाषा के inventor Arthur Whitney हैं, जो एक screen में जितना संभव हो उतना भरने के लिए बहुत concise और दूसरों को लगभग समझ न आने वाला code लिखते हैं
      निजी उदाहरण के तौर पर, बड़े switch statement में business logic रखने वाले विशाल Windows message handling function, यानी WndProc, को पढ़ना, समझना और debug करना, message handlers को अलग functions में तोड़ने वाले Visual C++ version की तुलना में कहीं आसान था
      एक और example में, microcontroller sample code में ADC usage example का एक version एक ही file में था और दूसरा main.c, config.c, interrupts.c, timer.c जैसी कई files में बंटा था। 200 lines से कम होने के बावजूद, दूसरा version context switching के कारण समझना कठिन था
    • मैंने बहुत बार देखा है कि लोग ऐसे linear code को भी आदतन अलग functions में निकाल देते हैं जिसे reuse नहीं किया जाना है
      ऐसे code snippets आम तौर पर class के private functions बन जाते हैं, और state रखते हैं। private function होने के कारण इन्हें सच में test करना भी कठिन होता है
      अब बहुत सारे private functions हो जाते हैं जो सिर्फ एक बार call होते हैं और आम तौर पर side-effect state को modify करते हैं। अगर वे caller के ठीक पास हों तो simple cases में अभी भी पढ़े जा सकते हैं, लेकिन समय के साथ कोई calling function और निकाले गए function के बीच कोई दूसरा function जोड़ देता है
      तब, call graph देखे बिना या class file में search किए बिना यह पता न चलने वाले code snippets अलग-अलग side-effect state को modify करने लगते हैं कि उन्हें कहाँ से call किया जाता है
      अगर code को non-linear बनाना ही है, तो जहाँ language support करती हो, निकाले गए private function को calling function का inner function बनाने पर कम से कम विचार करना चाहिए। इससे यह साफ हो जाता है कि उसे कहीं और से call नहीं किया जाता
      वास्तविक codebase में यह भी या तो-या का मामला नहीं, बल्कि दोनों को मिलाकर readable और maintainable form बनाने की कला के ज़्यादा करीब है
    • अगर function सचमुच linear है तो लंबा function भी इतना बुरा नहीं है। लेकिन वास्तविक example linear नहीं है और उसमें कई branches हैं
      क्या लोग उन सभी branches को test करेंगे? या सिर्फ pizza डालने वाला एक test लिखकर मोटे तौर पर देखेंगे कि चलता है या नहीं? बाहर से कई branches को test करना आम तौर पर झंझट वाला होता है, और छोटे, specialized functions को test करने से ज़्यादा परेशान करने वाला है, इसलिए दूसरा विकल्प ज़्यादा संभावित लगता है
  • “लीनियर code scale नहीं करता” कहना असल में उल्टा है। बड़े codebase में असली बुरा सपना वे छोटे, संक्षिप्त functions बनते हैं जिनके गहराई तक nested call stacks होते हैं
    नया code कहाँ जोड़ना है, यह स्पष्ट नहीं होता, और code किन-किन paths से call हो सकता है उन्हें trace करना पड़ता है, इसलिए बदलाव के प्रभाव को समझने की कठिनाई तेजी से बढ़ती है, साथ ही duplicate sub-routines भी बन जाते हैं
    99% मामलों में आपने कोई अच्छा abstraction नहीं बनाया होता, इसलिए बस linear code लिखना बेहतर है। संदिग्ध function semantics की बजाय copy/paste पसंद है

    • एक और जोखिम यह है कि अगर आप print_table() जोड़ते हैं, तो कोई उसे ढूँढ़कर अपने code में इस्तेमाल करेगा और अपने use case के हिसाब से output adjust करने के लिए कोई छोटा flag जोड़ देगा
      12 महीने बाद यह ऐसा दिखेगा:
      print_table(
      rows,
      headers = None,
      is_unicode = False,
      left_align = False,
      align = [],
      remove_emoji = None,
      max_width = 80,
      potato_mode = 7,
      _debug_frontend = not FLAGS.dont_debug,
      ellipsis_for = 0,
      no_print = False,
      )
    • यह readability की समस्या समझाता है, और मूल रूप से यही कहता है कि readability scalability को नुकसान पहुँचाती है
      इस तथ्य को छोड़कर कि readability scalability को प्रभावित कर सकती है, अगर दोनों concepts को orthogonal मानें, तो linear code modular code जितना अच्छी तरह scale नहीं करता। यह dichotomy जानने लायक है और परिस्थिति के अनुसार सोचने लायक भी
      फिर भी मैं सहमत नहीं हूँ। अगर छोटा function pure function है, तो वह readability problem पैदा नहीं करता। इसका मतलब है कि वह state को नहीं छूता, code में logic inject नहीं करता, और dependency injection तथा functions को दूसरे functions में pass करने को स्पष्ट रूप से न्यूनतम रखना चाहिए
      केवल data pass करने वाले pure functions की pipeline बनाएं, तो code readable और scalable हो जाता है। design flaws की वजह से logic दोबारा लिखने के मामले काफी कम हो जाते हैं, और pure functions को compose करने पर code Lego जैसा हो जाता है। refactoring भी मौजूदा primitive elements को फिर से arrange और recombine करने जैसा हो जाता है
  • Example code कम distracting होता अगर उसने कम से कम pizza metaphor को meaningful बनाए रखने की कोशिश की होती, या वह low-quality Go code न होता
    prepare function name के रूप में भयानक है। कोई अनुभवी Gopher शायद NewPizzaFromOrder जैसा नाम देता
    addToppings को अलग function रखने की वजह नहीं दिखती। अगर इसकी सचमुच जरूरत होती, तो व्यक्तिगत रूप से मैं इसे Pizza का method बनाता, जैसे func (p *Pizza) WithToppings(topping ...Topping) *Pizza { /* ... */ }। असली pizza mutable होता है, इसलिए method receiver को बदलता है
    हर बार pizza bake करते समय नया oven instantiate करने की वजह भी समझ नहीं आती। पहले से मौजूद oven से शुरू करके oven.Preheat() करें और oven.Bake(pizza) call करें। इससे आगे, oven.Preheat() को ऐसा नया type of Oven return करवाया जा सकता है जो .Bake() expose करता हो, ताकि बिना preheat किए bake करने की गलती compile stage पर ही रोकी जा सके। दूसरी जगह Baker interface हो सकता है, और ToasterOven implementation भी हो सकता है जहाँ preheating इतनी जरूरी न हो, इसलिए उसकी जरूरत न पड़े
    code न बदलते हुए भी declaration order को expected flow के हिसाब से rearrange करता। इससे एक-दूसरे को call करने वाले functions को scan करते समय page में ऊपर-नीचे कूदना नहीं पड़ता
    समय नहीं है, इसलिए यहीं रुकता हूँ, लेकिन यह code “कौन सा ज्यादा readable है” बहस शुरू करने के लिए भी पहले से ही बहुत खराब example है

  • John Carmack ने भी लगभग यही बात कही थी, और तब से मैं लगातार इसे follow कर रहा हूँ। linear code execution order को follow करता है, इसलिए स्वाभाविक रूप से readable होता है और आँखों की छलांग को minimize करता है
    कुछ code reuse के लिए non-linear होना चाहिए, और तब execution एक graph बन जाता है। अगर code graph structure के reuse का फायदा नहीं उठा रहा है, तो जहाँ एक edge काफी है वहाँ vertex introduce करने की जरूरत नहीं
    http://number-none.com/blow/blog/programming/2014/09/26/carm...

    • Carmack ने जो कहा था लेकिन original text में नहीं है, वह यह है कि अगर side-effect-free logic को अलग function में निकाला जा सकता है, तो आम तौर पर यह अच्छा idea होता है
      इस case में बाईं तरफ का code अगर pizza.Toppings = get_pizza_toppings(order.kind) जैसा होता, तो main function में pizza modification केंद्र में बनी रहती और वह बेहतर होता
  • मैं कुछ हद तक सहमत हूँ कि linear code ज़्यादा पढ़ने योग्य होता है, लेकिन सिर्फ यही अपने-आप अच्छी coding practice नहीं बन जाता
    अच्छा linear code ज़्यादा पढ़ने योग्य लगता है, लेकिन maintainability और testability काफी कम हो जाती है। मेरे पास दशकों का अनुभव है और मैं CS students के external assessment भी करता हूँ; इतने सालों में असल दुनिया में देखी गई अच्छी practices में जो बात पक्की लगी, वह सिर्फ functions को छोटा रखना थी
    मुझे abstraction से कोई खास लगाव नहीं है, और मैं यह भी नहीं मानता कि code duplication को हर हाल में टालना चाहिए, लेकिन अगर आप जितना हो सके single-purpose के करीब functions बनाते हैं, तो भविष्य का आपका खुद आप का शुक्रगुजार होगा
    उदाहरण जैसा code अगर 10 साल तक production में चलता है, तो उसके हर section में बदलाव आएंगे। किस्मत अच्छी हो तो comments भी update होंगे, लेकिन अधिकतर ऐसा नहीं होगा। unit tests भी बड़े और संभालने में मुश्किल होते जाएंगे और धीरे-धीरे ढीले पड़ेंगे; कोई व्यक्ति किसी ऐसे test हिस्से को बदलना भूल सकता है जो बदलाव से साफ तौर पर जुड़ा हुआ न दिखे। code भी समय के साथ कम readable होने की संभावना रखता है। यह इरादे या अक्षमता की वजह से नहीं, बल्कि time pressure जैसे मानवीय कारणों से होता है
    एक perfect दुनिया में concerns को अलग करने की जरूरत नहीं होती, लेकिन हम imperfect दुनिया में रहते हैं, और functions जितने छोटे और कम जिम्मेदारी वाले होंगे, समय के साथ उस imperfection को संभालना उतना आसान होगा

    • सही है, test करना कम आसान है, लेकिन इस उदाहरण में ये state changes हैं जिन्हें एक खास क्रम में किया जाना चाहिए
      अगर आप किसी object को specific states के flow से गुजार रहे हैं, तो मेरी राय में या तो इसे तोड़कर types से transitions दिखाएं, या फिर इसे एक बड़े function के रूप में लिखें। उदाहरण के लिए, अगर bakePizza RawPizza लेता है और BakedPizza लौटाता है, तो call order को compile time पर enforce किया जा सकता है
      readability, correctness और testability की वजह से मैं पहले तरीके को पसंद करता हूँ, लेकिन ज्यादातर programming languages में object type बदलने के लिए नया object बनाना पड़ता है और runtime cost आती है। अगर यह hot code path है, तो in-place mutation वाजिब है, और उस स्थिति में इसे एक ही linear function में रखना बेहतर है
    • हाल ही में Sussman की Software Design for Flexibility पढ़नी शुरू की है, और यह बात सीधे इसी से जुड़ी है
      https://mitpress.mit.edu/9780262045490/
  • John Carmack का संबंधित email: http://number-none.com/blow/blog/programming/2014/09/26/carm...
    चर्चा: https://news.ycombinator.com/item?id=12120752

  • मजबूत सहमति। पहले मैं उल्टी camp में था
    यहाँ मूल tension एक तरफ locality of behaviour और दूसरी तरफ high-level “table of contents” view को साफ दिखाने की इच्छा के बीच है। readable code के लिए locality ज्यादा महत्वपूर्ण है। लेख में जैसा कहा गया है, table-of-contents वाला नजरिया section comments से पर्याप्त साफ बनाया जा सकता है
    linear code को प्राथमिकता देने की एक और अहम वजह भी है। पूरे codebase में navigate करते समय “chunks”, यानी functions, classes या language द्वारा enforce की गई units, अगर मोटे तौर पर business use cases से मेल खाती हों तो यह काफी आसान हो जाता है। वरना search space बहुत बड़ा हो जाता है, और आपको टुकड़ों से पूरा चित्र खुद reconstruct करना पड़ता है। code structure को यह काम आपके लिए करना चाहिए
    अगर कई “चीजें” एक ही काम, जैसे signup या purchase, से संबंधित हैं, तो code में भी उन्हें एक साथ रखना बेहतर है। उन्हें ढूंढना और बदलना कहीं आसान होता है। sub-functions में सिर्फ तब बांटें जब reuse की जरूरत हो; केवल organization के लिए न बांटें
    [0] https://htmx.org/essays/locality-of-behaviour/

    • मैं उल्टी दिशा में गया। पहले linear code camp में था, लेकिन अब और ज्यादा functions की तरफ हूँ
      सबसे बड़ा कारण state है। function जितना लंबा होगा, local variables का scope उतना बड़ा होगा। function में कहीं भी कोई भी variable बदला जा सकता है, और data flow तुरंत स्पष्ट नहीं होता। ज्यादा functions होने पर scope छोटा रहता है और data flow ज्यादा explicit होता है
      side effect के तौर पर indentation भी कम हो जाती है
      साथ ही, मुझे बहुत छोटे functions भी पसंद नहीं हैं, क्योंकि तब असली काम कहाँ हो रहा है यह ढूंढना मुश्किल हो जाता है
    • “sub-functions में सिर्फ तब बांटें जब reuse की जरूरत हो, सिर्फ organization के लिए न बांटें” — तो testing कैसे करेंगे? दिमाग में रखनी पड़ने वाली state को कम करना? resources release करना? changes के impact को समझना?
      एक end-of-day processing की कल्पना करें जिसमें 10 non-reusable steps हैं जिन्हें क्रम से चलना है, और हर step 100 lines का है। हर step पिछले step से मिलते-जुलते, लेकिन समान नहीं, data का इस्तेमाल करता है। क्या आप सच में 1000 lines का single function चुनेंगे?
  • दोनों linear तरीके से पढ़े जाते हैं। छोटे functions निकालने वाले version में page के ऊपर एक table of contents है और steps के बीच data flow का summary है। अगर पूरा पढ़ना है, तो यह आकर्षक reading order लगता है
    लेकिन यह readability बनाए रखने के लिए, जब step order बदले तो function की position भी बदलनी होगी। अगर वे private functions हैं और सिर्फ table of contents से call होते हैं, तो ठीक है। लेकिन कोई चीज order को बनाए रखने के लिए मजबूर नहीं करती, और न ही पूरे reading flow के बारे में सोचने के लिए मजबूर करती है
    जब function reuse होना शुरू हो जाता है, तो अक्सर उसे linearize करना संभव नहीं रह जाता। कभी-कभी लोग हार मानकर alphabetic order में sort कर देते हैं, या फिर order बस random हो जाता है

  • अनुभव से लगता है कि कोई व्यक्ति जितना ज़्यादा code से परिचित होता है, उतना ही वह सोचता है कि code को छोटे functions में धकेलना ही सही रास्ता है
    क्योंकि उसने उस code का mental model पहले से बना रखा होता है, इसलिए उसके लिए सबसे साफ implementation वही होती है जिसमें lines बहुत कम हों
    लेकिन जब अगला व्यक्ति आता है, तो मूल context के बिना वही mental model बनाने के लिए उसे इधर-उधर जाते हुए अपने दिमाग़ के stack को push/pop करना पड़ता है, और यह कहीं ज़्यादा कठिन है

    • अगर code समझ में आता है, तो ऐसा नहीं होता। सुंदर abstraction, पतले interfaces और उचित documentation वाला अच्छी तरह लिखा code हो, तो इतना ज़्यादा इधर-उधर जाने की ज़रूरत नहीं पड़ती
      उदाहरण के लिए, आप अपनी इस्तेमाल की जाने वाली भाषा की standard library का source code कितनी बार पढ़ते हैं? लगभग कभी नहीं; आम तौर पर method signature देखते हैं और अगर कुछ थोड़ा जटिल या नया हो तो documentation पढ़ते हैं
      interface का मूल उद्देश्य यह है कि आप इस बात की चिंता न करें कि method कैसे implement हुआ है, बल्कि सिर्फ़ यह देखें कि वह क्या करता है। यह context, नाम और documentation के संयोजन से समझाया जाता है। लेकिन कई developers इसे समझते नहीं या इसकी परवाह नहीं करते, इसलिए linear हो या modular, वे ऐसा code लिखते हैं जो समझ में नहीं आता
      उदाहरण के लिए, अगर किसी service class में एक method call करके कुछ data लेना हो, दूसरे method से दूसरा data लेना हो, और तीसरे method से ऐसा data लेना हो जिसे पहले दोनों के साथ मिलाना पड़े, तो उस service का मतलब क्या है? यह अंदरूनी complexity को पूरी तरह बाहर expose करने जैसा है
      बात छोटे methods को ज़बरदस्ती लागू करने की नहीं है। ऐसे 20 functions जिनमें 5 lines हों, जिन्हें सिर्फ़ एक बार call किया जाता हो, जो बहुत specific काम करते हों और जिन्हें सही क्रम में ही call करना पड़े—उनका कोई मतलब नहीं। वह clean code नहीं, बल्कि cargo cult programming के ज़्यादा करीब है
      अहम बात यह है कि abstraction इस तरह की जाए कि नए team members और अनुभवी team members दोनों के लिए बात समझ में आए, reasoning आसान हो, और complexity उचित जगह पर छिपी रहे। यह आसान नहीं है, लेकिन संभव है
    • मैं सहमत नहीं हूँ, लेकिन शायद फर्क उन लोगों के बीच हो सकता है जो bottom-up तरीके से पढ़ते और सोचते हैं और जो top-down तरीके से सोचते हैं
      मेरा बेटा काफ़ी होशियार था, फिर भी school में संघर्ष करता था, और कई experts में से एक ने समझाया कि school आम तौर पर bottom-up तरीके से पढ़ाते हैं, लेकिन मेरा बेटा बहुत top-down learner है। उसे details में जाने से पहले पहले overview चाहिए होता है, जबकि दूसरे लोग पहले details समझते हैं और फिर overview जोड़कर बनाते हैं। school आम तौर पर दूसरे group के हिसाब से पढ़ाते हैं
      programmers के बीच भी ऐसा ही फर्क हो सकता है
    • “अगले व्यक्ति को इधर-उधर जाना पड़ेगा” वाली बात तभी लागू होती है जब वह व्यक्ति code पढ़ नहीं पाता। code को कम-से-कम शुरुआत में उसी तरह पढ़ना चाहिए जैसे वह लिखा गया है; अगर आप उसे execution order में पढ़ने की कोशिश करते हैं, तो यह गलत तरीका है
      अगर पिछले developer ने BakePizza function लिखा है, तो मान लें कि pizza ठीक से bake होगा और अगली line पर बढ़ जाएँ। restaurant कैसे चलता है यह समझने की कोशिश करते हुए अगर आप oven temperature जैसी details में फँस जाते हैं, तो न तो समझ पाएँगे कि restaurant कैसे चलता है और न ही सही oven temperature याद रहेगा
    • इसलिए हमें projectional code editor जैसे बेहतर tools की ज़रूरत है
      editor में functions को अस्थायी रूप से inline करने का toggle होना चाहिए। फिर आगे-पीछे जाने की ज़रूरत नहीं रहेगी