- छोटी 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 टिप्पणियां
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 के समान स्तर पर होने चाहिए
मेरे साथ काम कर चुके एक व्यक्ति ने “पढ़ने में आसान है” के नाम पर हर boolean condition को function में निकाल दिया, और “comments खराब होते हैं” के नाम पर बिल्कुल comments नहीं लिखे। मुझे वह किताब इसलिए पसंद नहीं, क्योंकि वह ऐसी खराब सलाह को आँख मूँदकर मानने वाले कट्टर समर्थक बना देती है
कभी-कभी 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, मुझे फर्क नहीं पड़ता; वह फिर भी ठीक है
cooking recipes भी पहले से ही काफी abstracted होती हैं। “प्याज़ को हल्का sauté करें” कहने का मतलब है कि आपको प्याज़ काटना और हल्का sauté करने का algorithm पहले से पता है। अगर सब कुछ inline लिख दिया जाए तो पढ़ा ही नहीं जा सकेगा
code भी ऐसा ही है। abstraction को सख्ती से बाहर कर दें तो आप भाषा द्वारा allowed सबसे निचले level तक उतर जाते हैं, और वह निश्चित रूप से readable code नहीं होता। उदाहरण के लिए, Python के
decodemethod के बजाय Unicode decoding खुद करने की कोशिश करें, तो program वास्तव में क्या कर रहा है यह समझना बहुत मुश्किल हो जाएगा। भाषा simple और well-tested abstraction देती है, इसलिए कोई ऐसा नहीं करता; फिर अपने द्वारा simple और well-tested abstraction बनाकर उसे business logic में इस्तेमाल करने से यह अलग कैसे हैमुश्किल हिस्सा ऐसी abstraction बनाना है जो इतनी अच्छी चुनी गई हो कि किसी को उसे फिर छूने की ज़रूरत न पड़े
अभी code lines मिलती-जुलती दिख रही हैं, इसका मतलब यह नहीं कि आगे भी उन्हें एक जैसा होना चाहिए या एक जैसा बनाए रखना चाहिए। “code लगभग repeat हो रहा है” इस वजह से दो अलग-अलग use cases को जबरन मिलाएँ, तो समय के साथ यह ऐसी abstraction बन जाती है जो किसी चीज़ को abstract नहीं करती
अगर use cases बहुत अलग हो जाएँ, तो implementation बहुत सारा logic caller की तरफ धकेल देती है, या अंतर को flags के रूप में expose करके अंदर दो अलग-अलग implementations को साथ-साथ रख देती है। पहला shallow abstraction है इसलिए उसका value कम है, और दूसरा स्वतंत्र implementations की तुलना में कम स्पष्ट है
उदाहरण कोड इतना सरल है कि स्वाभाविक रूप से linear code ज़्यादा पढ़ने योग्य लगता है, लेकिन वह idea अच्छी तरह scale नहीं होता
reusability या unit testability पर भी विचार करना चाहिए, और अगर सारा code एक ही function में डाल दें, तो पढ़े जा रहे code block से संबंधित हो भी सकने वाले और नहीं भी सकने वाले सभी local variables scope में रहते हैं, जिससे reasoning और कठिन हो सकती है
हालांकि कम अनुभव वाले दिनों को याद करूँ तो, कई बार ठीक-ठाक linear code को ज़रूरत से ज़्यादा modularize करके ऐसा code बना दिया था जिसमें इधर-उधर कूदना पड़ता था और जो कम maintainable था। शुरुआती रूप उस समय दिमाग में चल रहे सोच के flow के ज़्यादा करीब होता है, और पाठक भी शायद उसी तरह समझेगा—यह इसका फायदा है। अत्यधिक refactoring करने पर यह बात खो सकती है
आखिरकार programming एक craft के ज़्यादा करीब है, इसलिए परिस्थिति के हिसाब से चुनाव करने में अनुभव मदद करता है
उसका मकसद सिर्फ एक था। 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 होते तो डर सिर्फ ऐसे थे जो वास्तविकता नहीं बनते
किसी बिंदु पर एहसास होता है कि उन दर्जनों functions को एक खास order में ही call किया जाना चाहिए, और हर एक सिर्फ एक बार इस्तेमाल होता है। आखिरकार इसका मतलब है कि अगर कोई उन functions को उपयोगी ढंग से इस्तेमाल करना चाहे, तो उसे किसी जादुई composition order को जानने के लिए मजबूर किया जा रहा है
विशाल linear function अक्सर ज़्यादा readable और desirable होने का मुख्य कारण यह है कि कई concepts और relationships को context switch किए बिना एक ही chunk में साथ-साथ रखकर समझना आसान हो जाता है। इसके एक चरम समर्थक K भाषा के inventor Arthur Whitney हैं, जो एक screen में जितना संभव हो उतना भरने के लिए बहुत concise और दूसरों को लगभग समझ न आने वाला code लिखते हैं
निजी उदाहरण के तौर पर, बड़े
switchstatement में 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 के कारण समझना कठिन थाऐसे code snippets आम तौर पर class के
privatefunctions बन जाते हैं, और state रखते हैं।privatefunction होने के कारण इन्हें सच में test करना भी कठिन होता हैअब बहुत सारे
privatefunctions हो जाते हैं जो सिर्फ एक बार 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 करती हो, निकाले गए
privatefunction को calling function का inner function बनाने पर कम से कम विचार करना चाहिए। इससे यह साफ हो जाता है कि उसे कहीं और से call नहीं किया जातावास्तविक codebase में यह भी या तो-या का मामला नहीं, बल्कि दोनों को मिलाकर readable और maintainable form बनाने की कला के ज़्यादा करीब है
क्या लोग उन सभी 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 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 न होता
preparefunction 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 ofOvenreturn करवाया जा सकता है जो.Bake()expose करता हो, ताकि बिना preheat किए bake करने की गलती compile stage पर ही रोकी जा सके। दूसरी जगहBakerinterface हो सकता है, औरToasterOvenimplementation भी हो सकता है जहाँ 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...
इस 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 को संभालना उतना आसान होगा
अगर आप किसी object को specific states के flow से गुजार रहे हैं, तो मेरी राय में या तो इसे तोड़कर types से transitions दिखाएं, या फिर इसे एक बड़े function के रूप में लिखें। उदाहरण के लिए, अगर
bakePizzaRawPizzaलेता है औरBakedPizzaलौटाता है, तो call order को compile time पर enforce किया जा सकता हैreadability, correctness और testability की वजह से मैं पहले तरीके को पसंद करता हूँ, लेकिन ज्यादातर programming languages में object type बदलने के लिए नया object बनाना पड़ता है और runtime cost आती है। अगर यह hot code path है, तो in-place mutation वाजिब है, और उस स्थिति में इसे एक ही linear function में रखना बेहतर है
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/
सबसे बड़ा कारण state है। function जितना लंबा होगा, local variables का scope उतना बड़ा होगा। function में कहीं भी कोई भी variable बदला जा सकता है, और data flow तुरंत स्पष्ट नहीं होता। ज्यादा functions होने पर scope छोटा रहता है और data flow ज्यादा explicit होता है
side effect के तौर पर indentation भी कम हो जाती है
साथ ही, मुझे बहुत छोटे functions भी पसंद नहीं हैं, क्योंकि तब असली काम कहाँ हो रहा है यह ढूंढना मुश्किल हो जाता है
एक 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 भी बदलनी होगी। अगर वे
privatefunctions हैं और सिर्फ 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 करना पड़ता है, और यह कहीं ज़्यादा कठिन है
उदाहरण के लिए, आप अपनी इस्तेमाल की जाने वाली भाषा की 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 उचित जगह पर छिपी रहे। यह आसान नहीं है, लेकिन संभव है
मेरा बेटा काफ़ी होशियार था, फिर भी school में संघर्ष करता था, और कई experts में से एक ने समझाया कि school आम तौर पर bottom-up तरीके से पढ़ाते हैं, लेकिन मेरा बेटा बहुत top-down learner है। उसे details में जाने से पहले पहले overview चाहिए होता है, जबकि दूसरे लोग पहले details समझते हैं और फिर overview जोड़कर बनाते हैं। school आम तौर पर दूसरे group के हिसाब से पढ़ाते हैं
programmers के बीच भी ऐसा ही फर्क हो सकता है
अगर पिछले developer ने
BakePizzafunction लिखा है, तो मान लें कि pizza ठीक से bake होगा और अगली line पर बढ़ जाएँ। restaurant कैसे चलता है यह समझने की कोशिश करते हुए अगर आप oven temperature जैसी details में फँस जाते हैं, तो न तो समझ पाएँगे कि restaurant कैसे चलता है और न ही सही oven temperature याद रहेगाeditor में functions को अस्थायी रूप से inline करने का toggle होना चाहिए। फिर आगे-पीछे जाने की ज़रूरत नहीं रहेगी