Code ရေးရင် မကြွယ်နဲ့

Code ရေးရင် မကြွယ်နဲ့

Programmer လုပ်သက်မှာ ဘယ်သူမဆို အရမ်းလှပသပ်ရပ်တဲ့ ကုဒ်အပိုင်းတစ်ခုတော့ ရေးဘူးစမြဲပဲ။ အရမ်းအသုံးဝင်တဲ့ reusable function လေးဖြစ်ရင်ဖြစ်မယ်၊ Layer လေးတွေသေချာခွဲထားတဲ့ Code လေးဖြစ်ရင်ဖြစ်မယ်။ ဘယ်လိုဖြစ်ဖြစ် သင့်ဟာသင် ပြန်ကြည့်ပြီး ဂုဏ်ယူရတဲ့ Code အပိုင်းအစတစ်ခုဖြစ်မယ်။​ ဒါပေမဲ့ ခင်ဗျားသတိထားမိလား၊​ နောက် business logic အသစ်တွေ၊ requirements တွေများလာတာနဲ့ ကိုယ်ရေးထားတဲ့၊ဂုဏ်ယူရတဲ့ API လေးဆိုတော့ မလွှင့်ပစ်ရဲပဲ​ logic တွေထပ်ထပ်ထည့်တဲ့ အခါမှာ တစ်ဖြည်းဖြည်းပွထလာပြီး ၃ လ၊​၆ လ၊ ၁ နှစ်ကြာတော့ ဘယ်လိုမှ maintain လုပ်လို့မရတဲ့ spaghetti code ဖြစ်လာတယ်။

Premature Abstraction is the root of all evil

System အကြီးကြီးတွေ တည်ဆောက်တဲ့အခါမှာ အစိတ်အပိုင်းသေးသေးတစ်ချို့ကို reusable ဖြစ်အောင် ရေးကြရတယ်။ တကယ်တန်းမှာတော့ မလိုအပ်တဲ့ နေရာမှာ reusable ဖြစ်အောင်ရေးမိရင် ကုဒ်ကို ပိုရှုပ်ထွေးစေတယ်၊ complexity ပိုများလာမယ်။​ ဥမာအနေနဲ့ ဘယ်လိုမျိုးလဲဆိုရင် စစကရေးထားတုန်းကတော့ null ဖြစ်နိုင်ချေမရှိဘူး၊​ နောက် scenario တွေမှာကျ null ဖြစ်နိုင်လောက်တယ်လေလို့ တွေးပြီးရေးလိုက်တော့ ထွက်လာတဲ့ ရလဒ်က null ဖြစ်ရင် ဘာမှမလုပ်တဲ့ do nothing function လိုမျိုးတွေ။​

const trackWidgetView = (someAnalytcisValue? : string) => {
    if (someAnalytcisValue) {
        Firebase.track(someAnalytcisValue)
    }

}

class BaseWidget {
  someAnalytcisValue?: string;
  
  onRender() {
    trackWidgetView(this.someAnalytcisValue)
  }
}
 

နောက်တစ်မျိုးကြည့်မယ်ဆိုရင် မတူညီတဲ့ implementation နှစ်ခုကို အတင်းစွဲညှိထားတာမျိုး။

interface IAnaylticsPlugin {
    trackAction(values: Map<string, any>)
    trackEvent(values: Map<string, any>)
}

class AdobeAnalyticsPlugin implements IAnaylticsPlugin {
    trackAction(values: Map<string, any>) {
        Adobe.track(AdobeAnalyticsType.Action, values)
    }
    trackEvent(values: Map<string, any>) {
        Adobe.track(AdobeAnalyticsType.Event, values)
    }
}

class FirebaseAnalyticsPlugin implements IAnaylticsPlugin {
    trackAction(values: Map<string, any>) {
       //DO NOTHING
    }
    trackEvent(values: Map<string, any>) {
        Firebase.log(values)
    }
}

const analyticsPlugin : Array<IAnaylticsPlugin> = [new AdobeAnalyticsPlugin() , new FirebaseAnalyticsPlugin()]

const trackEvent = (values: Map<string, any>) => {
    analyticsPlugin.forEach((plugin) => {
        plugin.trackAction(values)
    })
}

ဒီမှာကြည့်မယ်ဆို Firebase မှာက action ရယ် event ရယ်ဆိုပြီး ခွဲထားတာမျိုးမရှိဘူး။​ ဒီလိုမတူတာတွေကို ကုဒ်ကြည့်လို့လှယုံသက်သက်နဲ့ ဆွဲစုထားတဲ့အခါမှာ နောက်ပိုင်း analytics platform တွေထပ်ထည့်ဖို့ပိုခက်သွားမယ်။​ ဒီလိုရေးမဲ့အစား ရှင်းရှင်းလေးဒီလိုရေးလို့ရတယ်။

class AdobeAnalytics {
    trackAction(values: Map<string, any>) {
        Adobe.track(AdobeAnalyticsType.Action, values)
    }
    trackEvent(values: Map<string, any>) {
        Adobe.track(AdobeAnalyticsType.Event, values)
    }
}

class FirebaseAnalytics  {
    logEvent(values: Map<string, any>) {
        Firebase.log(values)
    }
}
const adobeAnalytics = new AdobeAnalytics()
const firebaseAnalytics = new FirebaseAnalytics()

const trackAction = (values: Map<string, any>) => {
    adobeAnalytics.trackAction(values)
}
 
const trackEvent= (values: Map<string, any>) => {
    adobeAnalytics.trackAction(values)
    firebaseAnalytics.logEvent(values)
}

ဒီလိုဆိုရင် ခမ်းခမ်းနားနား design pattern မဟုတ်ပေမဲ့ ရိုးရှင်းပြီးဖတ်လို့လည်းလွယ်တယ်။ ပြောချင်တဲ့ အဓိကအချက်က Code ရေးရင် မကြွယ်ဖို့။ ကျွန်တော်တို့ပြောပြောနေတဲ့ You aren't going to need it (YANGI), Keep it simple stupid (KISS) တို့ဆိုတာလည်း ပြောချင်တာက မလိုပဲ မကြွယ်နဲ့၊​​ ရိုးရှင်းအောင်သာရေး၊​ ကိုယ့်ကုဒ်ကို ဖတ်လို့ရဖို့ အရေးကြီးတယ်ဆိုတဲ့ အချက်ပဲ။

ကျွန်တော်သတိထားမိတာက များသောအားဖြင့် Clean code ကို စဖတ်တဲ့ Junior developer တော်တော်များများက အဲထဲက Don't Repeat Yourself (DRY) Principle ကို သေချာနားမလည်တဲ့အခါ ရှိသမျှ အရာတိုင်းကို reusable component တွေဖြစ်အောင် ရေးကြတယ်။ DRY ကသူက "Knowledge" ကို repeat မလုပ်ဖို့ပြောတာ၊ ကုဒ်ကိုပြောတာမဟုတ်ဘူး။​နောက်တစ်ခုက ကျောင်း၊တက္ကသိုလ်တွေ၊​ industry ထဲက ဆရာ၊ ဆရာမတွေက Function ဆို သေးသေးလေးထား၊​ လိုင်းဘယ်နှစ်ကြောင်းထပ်ပိုရင် ခွဲထုတ်ဆိုပြီး ပြောကြတဲ့အခါ Function မသေးသေးအောင်လိုက်ခွဲရင်း Function သေးသေးလေးတွေ ပွထနေတာကိုကြုံရတတ်တယ်။ အောက်က function ကိုတစ်ချက်ကြည့်ကြည့်ရအောင်။

const multiplyBy100 = (value: number): int => {
	return number * 100
}
Legit code from the project I'm working on as of this writing

ဒီ function က reusable ဖြစ်တယ်၊ ၁ လိုင်းထဲပဲရှိတယ်။ ကောင်းလားဆိုတော့ မကောင်းဘူး။ ဒီ function ကိုခေါ်သုံးနေမဲ့အစား ရှိပြီးသား ဒဲ့ 100 နဲ့မြှောက်တာကမှပိုလွယ်ဦးမယ်။​ ဒါနဲ့ပြောင်းပြန် အောက်ကကုဒ်ကိုကြည့်ကြည့်ရအောင်

const getUserProfile = () => {
	const client = new NetworkClient()
    const response = client.makeRequest('test.com/user')
    
    if (response.code == 200) {
    	const jsonParser = new JsonParser<User>(response)
        return jsonParser.parse(response)
    } else {
    	logError("user_profile_call", response)
        throw Error(new NetworkError(response.code))
    }
}

ဒီကုဒ်မှာဆို ဘာလို့ JSON Parse တဲ့အပိုင်းကို Reusable ဖြစ် အောင် function ခွဲမထုတ်လဲ။ Error logging ကကော ဘာလို့ ဒီထဲရောက်နေလဲလို့မေးစရာရှိတယ်။ သေချာစဥ်းစားကြည့်ရင် ဒီကုဒ်က အဓိပ္ပာယ်ရှိတယ်။ ဒါကိုခေါ်သုံးတဲ့သူက သူ့အနေနဲ့ ဘာမှသိစရာမလိုဘူး။​ User profile လိုချင်တိုင်းမှာ ဒီ function ကို ခေါ်လိုက်ရင်ရပြီ၊ ကျန်တာဘာမှ စိတ်ပူစရာမလိုဘူး။​ ရုတ်တရက် ကြည့်ရင် မဆိုင်တဲ့ အရာတွေပါတယ်ထင်ရပေမဲ့ များသောအားဖြင့် အသုံးလိုတဲ့နေရာအတွက် လု့လောက်တယ်။ ဒီကုဒ်ကို တစ်ခုခုပြင်ချင်တဲ့သူကလည်း ကြည့်လိုက်တာနဲ့ ဘာဖြစ်နေမှန်း တန်းမြင်ရတယ်။​​ ဥပမာ Logger လေးကို ပြောင်းလို့ရအောင် delegate လေးသုံးမယ်ဆိုပြီး လုပ်လိုက်ရင် အဲဒီ delgate ကဘယ်ကနေ လာတယ်၊ ဘယ်လိုအလုပ်လုပ်တယ်ဆိုတာ ထပ်ဖတ်နေရမယ်။

ဒီတော့ function တွေမခွဲရတော့ဘူးလား၊ abstraction တွေမလုပ်ရဘူးလားဆိုတော့ အဲလိုလည်း မဟုတ်ဘူး။​ အချိန်မရောက်သေးခင် မလုပ်ဖို့ပဲပြောတာ။​ အချိန်ရောက်မရောက် ဘယ်လိုသိနိုင်လဲဆိုရင် ကျွန်တော်ကတော့ abstraction ကိုမရင်းနှှီးသေးတဲ့သူအဖို့ Rule of Three ဆိုတာကို သုံးဖို့အကြံပေးတယ်။ ဒီနည်းက Martin Fowler က သူရဲ့ Refactoring : Improving the Design of Existing Code စာအုပ်ထဲမှာ ပြောထားတဲ့နည်းဖြစ်တယ်။ ပထမတစ်ကြိမ် လုပ်စရာရှိတာကိုလုပ်၊​ ဒုတိယအကြိမ်ကြ ပထမရေးထားတာကိုပဲ copy paste လုပ်၊ ဒါပေမဲ့ စိတ်ထဲမှာတော့မှတ်ထား။​ တတိယအကြိမ်ရောက်ပြီဆိုရင်တော့ refactor လုပ်ပြီး ကုဒ်ကိုပြင်ပေတော့။​ ဒီလိုလုပ်ခြင်းအားဖြင့် တော်တော်များများသောအချိန်တွေမှာ မလိုအပ်တဲ့ abstraction တွေကို လျှော့ချပေးနိုင်တယ်။ မဟုတ်မမှန်တဲ့ ယူဆချက်တွေရဲ့ ရန်ကလည်း ကာကွယ်ပေးတယ်။ နောက်တစ်ခုက refactor လုပ်ရင် မလိုတော့ဘူးလို့ထင်ရတဲ့ abstraction တွေကို ကိုယ်ဘယ်လောက်ပဲချစ်ချစ် ဖျက်ဖို့လည်းမမေ့ပါနဲ့။ တစ်ခါတစ်လေ de-abstract လုပ်ခြင်းသည်လည်း ကုဒ်အရည်အသွေးကို ကောင်းစေပါတယ်။

နောက်ကုဒ်ရေးရင် abstraction တွေခွဲ၊ function တွေကို သေးသေးလေးတွေလုပ်တော့မယ်ဆို ပြန်စဥ်းစားပြီး လိုအပ်မှ လုပ်ကြပါ။ မသေချာရင် အပေါ်ကပြောထားတဲ့နည်းကို သုံးပါ။​ Code ရေးတာ မကြွယ်မိစေပါနဲ့။

Show Comments