इंस्ट्रूमेंटेशन या मौत: मैंने Android प्रोजेक्ट में Gemini CLI को कैसे वश में किया
द्वारा Nikolay Vlasov
कोड जनरेट करना अब बहुत आसान हो गया है।
मेरा एक दिलचस्प अनुभव रहा: एक पेट-प्रोजेक्ट (pet project) में मैंने लगभग बिना किसी रिव्यू के "वाइब-कोडिंग" (vibe-coding) का प्रयास किया—इस सिद्धांत पर कि "एजेंट जो लिखता है, हम उसे स्वीकार करते हैं"। शुरू में यह बहुत तेज़ और सुविधाजनक लग रहा था। लेकिन बहुत ही जल्द प्रोजेक्ट बिखरने लगा: कोड भारी और जटिल (hard to maintain) हो गया, कुछ फंक्शन्स खराब हो गए, देरी, मेमोरी लीक—वे सभी समस्याएँ जो आम तौर पर महीनों में जमा होती हैं, यहाँ लगभग तुरंत सामने आ गईं।
एक समय पर यह साफ हो गया: समस्या यह नहीं है कि एजेंट "बुरा" कोड लिखता है। समस्या उस गति (speed) की है जिससे यह कोड बिना किसी नियंत्रण के जमा हो रहा था।
इस दृष्टिकोण में क्या खराब होता है
जब आप मैनुअल कंट्रोल हटा देते हैं, तो विशिष्ट लक्षण दिखाई देने लगते हैं:
- आर्किटेक्चरल लेयर्स को बायपास करना;
- लॉजिक का दोहराव (duplication);
- मॉडुलैरिटी की सीमाओं का धुंधला होना;
- आकस्मिक डिपेंडेंसी (accidental dependencies);
- बिना ज़रूरत के कोड को जटिल बनाना;
- परफॉरमेंस में धीरे-धीरे गिरावट।
ज़रूरी बात यह है कि यह सब एक बार में नहीं होता। हर एक कदम "सामान्य" लगता है, लेकिन कुल मिलाकर सिस्टम तेज़ी से नियंत्रण से बाहर हो जाता है।
सिर्फ एक प्रॉम्प्ट काफी क्यों नहीं था
मेरा पहला प्रयास काफी सीधा था—मैंने एक बड़ा rules.md तैयार किया जिसमें शामिल थे:
- आर्किटेक्चरल प्रतिबंध;
- नामकरण की परंपराएँ (naming conventions);
- लेयर्स के नियम;
- प्रोजेक्ट के स्थानीय समझौते।
इसने आंशिक रूप से मदद की, लेकिन समस्या का समाधान नहीं हुआ।
प्रैक्टिकल रूप से यह सामने आया कि:
- लंबी संदर्भ सीमा (long context) अस्थिर तरीके से काम करती है;
- समय के साथ कुछ नियमों को नज़रअंदाज़ कर दिया जाता है;
- मॉडल हमेशा प्रतिबंधों को लगातार लागू नहीं करता है;
- प्रॉम्प्ट के आकार के साथ लागत और जवाब देने का समय बढ़ जाता है।
अंत में, मैं एक व्यावहारिक निष्कर्ष पर पहुँचा: महत्वपूर्ण नियमों की केवल व्याख्या नहीं की जानी चाहिए, बल्कि उन्हें चेक भी किया जाना चाहिए।
1. Konsist: एक निष्पादन योग्य अनुबंध के रूप में आर्किटेक्चर
एजेंट डिफ़ॉल्ट रूप से सबसे आसान कार्यान्वयन मार्ग (implementation path) चुनता है। अगर किसी लेयर को बायपास करना संभव है, तो वह उसे करेगा।
इसे सीमित करने के लिए, मैंने Konsist (टेस्ट के माध्यम से Kotlin कोड के आर्किटेक्चरल नियमों की जाँच करने वाला टूल) का उपयोग करके टेस्ट के माध्यम से आर्किटेक्चर का वर्णन करना शुरू किया।
उदाहरण:
@Test
fun `use cases should have UseCase suffix and reside in domain package`() {
val classes = Konsist.scopeFromProject().classes()
val violations = classes
.filter { classDeclaration ->
classDeclaration.name?.endsWith("UseCase") != true ||
!classDeclaration.resideInPackage("com.core.domain")
}
if (violations.isNotEmpty()) {
val message = buildString {
appendLine("VIOLATION: UseCase naming conventions not followed")
appendLine("FIX: Rename class to end with 'UseCase'")
appendLine("FIX: Move class to com.core.domain package")
}
fail(message)
}
}
यहाँ मेरे लिए दो बातें महत्वपूर्ण हैं:
- चेक नियमों के उल्लंघन को पकड़ता है;
- मैसेज इसे ठीक करने के लिए एक स्पष्ट दिशा देता है।
एजेंट "शून्य" में नहीं, बल्कि फीडबैक लूप में काम करना शुरू कर देता है।
2. लॉग कंप्रेशन: कम शोर — तेज़ इटरेशन
लॉग का आकार (volume of logs) एक ऐसी समस्या थी जिसका मैंने सामना किया।
अगर आप एजेंट को पूरा Gradle या JUnit आउटपुट देते हैं, तो वह उस बड़े आकार में खो जाता है। कॉन्टेक्स्ट शोर (noise) से भर जाता है, सिग्नल (signal) से नहीं।
इसलिए मैंने एक साधारण कंप्रेशन लेयर बनाई:
- मैं केवल फेल हुए टेस्ट्स को रखता हूँ;
- मैं एक छोटा सा एरर मैसेज लेता हूँ;
- मैं स्टैकट्रेस को सीमित करता हूँ;
- मैं बाकी सब फ़ालतू हटा देता हूँ।
उदाहरण:
def parse_xml_reports(root_dir):
summary = []
for testcase in root.findall(".//testcase"):
failure = testcase.find("failure")
if failure is not None:
message = failure.get("message", "No message")
text = failure.text or ""
stacktrace = "\n".join(text.strip().split("\n")[:15])
summary.append(f"FAILED: {testcase.get('name')}")
summary.append(f"Message: {message}")
summary.append(f"Stacktrace:\n{stacktrace}\n")
return summary
इसके बाद "खराब हुआ → ठीक हुआ" चक्र काफी तेज़ और अधिक अनुमानित (predictable) हो गया।
3. Spotless और detekt: बिना मानवीय हस्तक्षेप के बुनियादी साफ़-सफाई
अगली लेयर ऑटोमेटेड क्वालिटी चेक है:
- Spotless (कोड की ऑटोमैटिक फॉर्मेटिंग के लिए एक बहुउद्देशीय टूल);
- detekt (Kotlin में संभावित समस्याओं और जटिल संरचनाओं को खोजने के लिए स्टैटिक एनालाइज़र)।
मैंने इन्हें "अतिरिक्त उपकरण" के रूप में देखना बंद कर दिया है—ये अब प्रक्रिया का हिस्सा हैं।
अगर कोड इन चेक्स को पास नहीं करता है, तो उसे तैयार नहीं माना जाता। एजेंट खुद वापस जाकर उसे ठीक करता है।
यह इन चीज़ों को खत्म करता है:
- रिव्यू के दौरान छोटी-मोटी गलतियाँ;
- स्टाइल को लेकर बहस;
- कोड की पठनीयता (readability) में धीरे-धीरे गिरावट।
4. अनुवाद: मैकेनिकल गलतियों को हटाना
strings.xml एक अप्रत्याशित समस्या का केंद्र निकला।
LLMs नियमित रूप से इनमें गलतियाँ करते हैं:
- अपोस्ट्रॉफी (apostrophes);
- कोट्स (quotes);
- एस्केप कैरेक्टर्स (escape sequences)।
मैंने टेक्स्ट के माध्यम से मॉडल को यह "सिखाने" की कोशिश नहीं की। इसके बजाय इन्हें जोड़ना आसान निकला:
- चेक मैकेनिज्म;
- ऑटोमैटिक करेक्शन;
- XML पार्सिंग के माध्यम से काम।
ज़रूरी बात: कोई बड़े ग्लोबल रिप्लेस नहीं—केवल ज़रुरत के हिसाब से सुधार।
5. परफॉरमेंस और साइज
समय के साथ एक अलग प्रभाव जो सामने आया, वह था उत्पाद की अदृश्य और धीमी गिरावट।
एजेंट एक समस्या का समाधान कर सकता है, लेकिन ऐसा करते समय वह:
- एक भारी डिपेंडेंसी जोड़ सकता है;
- कोड को जटिल बना सकता है;
- बिल्ड साइज (build size) बढ़ा सकता है;
- परफॉरमेंस को प्रभावित कर सकता है।
इसे ट्रैक करने के लिए मैंने जोड़ा:
- बुनियादी बेंचमार्क;
- बिल्ड साइज चेक।
यह पूरी गारंटी नहीं देता है, लेकिन कम से कम समय पर विचलन (deviations) देखने का अवसर देता है।
6. Pre-commit: क्विक फ़िल्टर (Mercurial/hg या Git)
मैंने कुछ चेक्स को pre-commit स्टेज पर ले लिया है:
- ऑटो-फॉर्मेटिंग;
- स्पेसिफिक लिंटिंग;
- क्रिटिकल मॉड्यूल्स के लिए आर्किटेक्चरल चेक्स।
इसका उद्देश्य "सब कुछ प्रतिबंधित करना" नहीं है, बल्कि:
- बुनियादी समस्याओं को जल्दी पकड़ना;
- उन्हें रिपॉजिटरी में न ले जाना;
- फालतू शोर के साथ रिव्यू पर बोझ न डालना।
यह क्यों काम करता है
डेवलपमेंट का मूल उद्देश्य वास्तव में नहीं बदला है। टेस्ट्स, लिंटर्स और आर्किटेक्चरल सीमाएँ हमेशा से मानक रही हैं।
बदला कुछ और है: वह गति जिस पर कोड सामने आ रहा है।
जब बदलाव जल्दी और बड़ी मात्रा में जनरेट होते हैं, तो मैनुअल कंट्रोल अब स्केल नहीं कर पाता। जिसे पहले "आँखों से पकड़ा" जा सकता था, अब उसे प्रोसेस करने का समय ही नहीं मिलता।
इस संदर्भ में, ऑटोमेटेड चेक्स अब एक "अच्छी प्रैक्टिस" (good practice) नहीं रह गए हैं और एक बुनियादी ज़रूरत बन गए हैं—सिर्फ सिस्टम को स्थिर रखने के लिए।
निष्कर्ष
मैं इस दृष्टिकोण को सभी के लिए सार्वभौमिक (universal) या अनिवार्य नहीं मानता।
लेकिन मेरे मामले में, इसने संज्ञानात्मक बोझ (cognitive load) को कम करके काफी ठोस प्रभाव पैदा किया।
अब मुझे ज़रूरत नहीं है:
- सभी प्रतिबंधों को ध्यान में रखना;
- हर बदलाव को मैन्युअल रूप से पढ़ना;
- बड़ी-बड़ी लॉग फाइल्स का विश्लेषण करना;
- बुनियादी चीज़ों को बार-बार चेक करना।
सिस्टम इसकी ज़िम्मेदारी ले लेता है, और मैं वहाँ जुड़ता हूँ जहाँ वास्तव में इसकी ज़रूरत होती है।
मुझे जो व्यावहारिक लाभ मिले हैं उनमें से कुछ:
- बिना किसी रैंडम रिग्रेशन के अधिक स्थिर इटरेशन;
- फीडबैक के माध्यम से एजेंट का अनुमानित व्यवहार (predictable behavior);
- समाधान का तेज़ चक्र;
- रिव्यू पर कम शोर;
- एजेंट के उपयोग को स्केल करना आसान।
यह सभी समस्याओं का समाधान नहीं करता है, लेकिन एजेंट के साथ काम करने की प्रक्रिया को काफी हद तक नियंत्रित करने योग्य बनाता है—कम से कम मेरे अनुभव में।
उपयोगी लिंक्स
- Konsist — Kotlin के लिए अपडेटेड आर्किटेक्चरल लिंट डॉक्यूमेंटेशन।
- Spotless — कोड की ऑटोमैटिक फॉर्मेटिंग के लिए एक टूल।
- detekt — Kotlin के लिए स्टैटिक कोड एनालाइजर।
- Mercurial (hg) — वर्जन कंट्रोल सिस्टम।
- Maestro — मोबाइल ऐप्स के UI टेस्ट ऑटोमेशन के लिए प्लेटफॉर्म।
अगले लेख में, मैं विस्तार से बताऊंगा कि मैंने MCP Mobile और Maestro (मोबाइल ऐप्स के सरल और डिक्लेरेटिव UI टेस्टिंग के लिए एक फ्रेमवर्क) के माध्यम से इसमें ऑटोमैटिक UI परिदृश्यों (scenarios) और इंटरफेस चेक्स को कैसे जोड़ा।