قبل از نوشتن کد، بهتر است بدانید که چگونه کد خود را مدیریت کنید و چگونه کد را مقیاس پذیر کنید.
همانطور که Robert C.Martin یا همون عمو باب که در کتاب خود گفته:
شما این مقاله را به دو دلیل میخوانید. اول، شما یک برنامهنویس هستید. دوم، شما میخواهید یک برنامهنویس بهتر باشید.
مانند آنچه او گفت، تصور کنید که در یک کتابخانه هستید، و دنبال تعدادی کتاب هستید. اگر کتابخانه کتابها را مرتب و طبقهبندی کرده باشد، کتابهای خود را سریعتر پیدا خواهید کرد. علاوه بر این، طراحی داخلی و معماری جالب باعث میشود در حالیکه در جستجوی کتاب هستید، در داخل کتابخانه احساس راحتی کنید.
درست مانند نوشتن کتاب، اگر میخواهید چیزی عالی بسازید، باید بدانید که چگونه کدها را منظم بنویسید و آنها را سازماندهی کنید. اگر عضو تیم هستید و یا شخص دیگری کد شما را دارد، فقط باید نام متغیرها، کلاسها و پکیجها را ببینند و فورا آنها را درک میکنند. و نیاز ندارد که دوباره از صفر شروع کنند.
کد تمیز(Clean Code) چیست؟
همانطور که مشاهده میکنید، اگر دیگران نتوانند کد شما را درک کنند، این خوب نیست که توسعهی خود را سریعتر انجام دهید.
کد شما تعریفی از "تمیز " دارد اگر توسط افراد موجود در تیم به راحتی قابل درک باشد. کد تمیز توسط یک توسعهدهنده غیر از نویسندهی اصلی آن قابل خواندن و بهبود است. با قابل فهم بودن قابل خواندن، تغییرپذیری، قابلیت توسعه و قابل نگهداری نیز است.
آیا باید به آن اهمیت دهم؟
دلیل اینکه باید به کد خود اهمیت دهید این است که کد شما روند تفکر شما را به دیگران توصیف میکند. به همین دلیل است که باید فکر کنید تا کد خود را زیباتر، ساده و خواندنیتر کنید.
ویژگیهای یک کد تمیز
- کد شما باید زیبا باشد: کد باید به روشی که یک جعبه موسیقی خوب و یا یک ماشین با طراحی زیبا شما را میخنداند باعث لبخند شما شود.
- کد شما باید مورد مراقبت قرار بگیرد: شخصی وقت خود را برای ساده و منظم نگهداشتن کد صرف کرده است. او به جزئیات توجه کافی داشته است. بنابراین از کد مراقبت شده است.
- کد شما باید متمرکز شود: هر تابع، هر کلاس، هر ماژول یک نگرش تک ذهنیتی را در معرض نمایش میگذارد که بدون آلودگی و با جزئیات اطراف باقی میماند.
- بدون تکرار است
- تمام تست ها را اجرا میکند
- تعداد موجودیت ها مانند کلاسها، متدها، توابع و موارد مشابه را به حداقل برسانید
یک تفاوت بین یک برنامهنویس هوشمند و برنامهنویس حرفهای این است که برنامهنویس حرفهای میفهمد که وضوح کد مهم است. حرفهایها از توانایی خود برا نوشتن کدی استفاده میکنند که دیگران میتوانند آن را درک کنند.-Robert C.Martin
نامهای معنیدار ایجاد کنید
انتخاب نامهای خوب به زمان نیاز دارد اما بیشتر از زمان لازم صرفهجویی میکند. نام یک متغیر، تابع یا کلاس باید جوابگوی تمام سوالات بزرگ باشد. باید به شما بگوید که چرا وجود دارد، چه کاری انجام میدهد و چگونه استفاده میشود. اگر یک نام نیاز به comment دارد، بنابراین این نام نیت خود را آشکار نمیکند.
بیایید که یک مثال ببینیم:
// Bad variables naming
var a = 0 // user ages
var w = 0 // user weight
var h = 0 // user height
// Bad functions naming
fun age()
fun weight()
fun height()
// Bad classes naming to get user data
class UserInfo()
// Best practices varibales naming
var userAge = 0
var userWeight = 0
var userHeight = 0
// Best practices functions naming
fun setUserAge()
fun setUserWeight()
fun setUserHeight()
// Best practices classes naming to get user data
class User()
نام کلاسها
کلاسها و اشیاء باید دارای اسامی و عباراتی مانند Custumer ,WikiPage ,Account و AddressParser باشند. از کلماتی مانند Manager, Processor, Data یا Info در نام کلاسها استفاده نکنید. نام کلاسها نباید فعل باشد.
نام متدها
نام متدها باید دارای فعل و یا عبارات فعلی باشند مانند postPayment, deletePage یا save. Accessors, mutators و predicate باید برای مقدار آنها نامگذاری شوند و با پیشوندهای get و set براساس استاندارد جاوا نامگذاری شوند.
از نامی در دامنهی مشکل استفاده کنید
وقتی هیچ نامی برای کاری که انجام میدهید پیدا نکردید، از نامی در دامنهی مشکل استفاده کنید. حداقل برنامهنویسی که کد شما را دارد میتواند از متخصص دامنه سوال کند که منظور شما چیست.
خوب، اکنون به نوشتن کد با اصول S.O.L.I.D ادامه میدهیم.
نوشتن کد با اصول S.O.L.I.D
این اصول توسط Robert C.Martin اختراع شده است، SOLID مجموعهای از اصول طراحی برا کد خوب، توصیف میکند.
اصل تک وظیفهای – SRP
یعنی هر کلاس مسئولیت واحدی داشته باشد. هرگز نباید بیش از یک دلیل برای تغییر کلاس ایجاد شود. فقط به این دلیل که میتوانید هر آنچه را که میخواهید در کلاس اضافه کنید به این معنی نیست که باید اضافه شود. کلاسهای بزرگ را به کلاسهای کوچکتر تقسیم کنید.
بیایید یک مثال را بررسی کنیم:
ما یک RecyclerView.Adapter با business logic درون onBindViewHolder داریم.
class MyAdapter(val friendList: List<FriendListData.Friend>) :
RecyclerView.Adapter<CountryAdapter.MyViewHolder>() {
inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var name: TextView = view.findViewById(R.id.text1)
var popText: TextView = view.findViewById(R.id.text2)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val friend = friendList[position]
val status = if(friend.maritalStatus == "Married") {
"Sold out"
} else {
"Available"
}
holder.name.text = friend.name
holder.popText.text = friend.email
holder.status.text = status
}
override fun getItemCount(): Int {
return friendList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_friendlist, parent, false)
return MyViewHolder(view)
}
}
این امر باعث میشود RecyclerView.Adapter از مسئولیت واحد برخوردار نباشد، زیرا business logic درون onBindViewHolder است. این متد تنها وظیفهی تنظیم دادهها را در viewBinding دارد.
اصل باز-بسته - OCP
موجودیتهای نرمافزاری باید برای گسترش باز باشند اما برای اصلاح بسته باشند. این بدان معنی است که اگر کلاسی به اسم A نوشتهاید، و هم تیمی شما بخواهد اصلاحی در عملکرد کلاس A بدهد. میتواند به جای تغییر در کلاس به راحتی با گسترش کلاس A این کار را انجام دهد.
یک مثال آسان برای آن RecyclerView.Adapter خواهد بود. شما میتوانید به راحتی این کلاس را گسترش داده و آداپتور دلخواه خود را با رفتارهای سفارشی و بدون تغییر در کلاس RecyclerView.Adapter ایجاد کنید.
class FriendListAdapter(val friendList: List<FriendListData.Friend>) :
RecyclerView.Adapter<CountryAdapter.MyViewHolder>() {
inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var name: TextView = view.findViewById(R.id.text1)
var popText: TextView = view.findViewById(R.id.text2)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val friend = friendList[position]
holder.name.text = friend.name
holder.popText.text = friend.email
}
override fun getItemCount(): Int {
return friendList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_friendlist, parent, false)
return MyViewHolder(view)
}
}
اصل جایگزینی Liskov - LSP
کلاسهای فرزند هرگز نباید تعاریف کلاس والد را بشکنند.
این بدان معنی است که زیرکلاس باید متدهای کلاس والد را override کند که این کار عملکرد کلاس والد را از بین نمیبرد. برای مثال یک اینترفیس میسازیم که دارای onClick() listener است، و سپس listener را در MyActivity اعمال میکنیم و وقتی onClick() فراخوانی شد یک toast به آن میدهیم.
interface ClickListener {
fun onClick()
}
class MyActivity: AppCompatActivity(), ClickListener {
//........
override fun onClick() {
// Do the magic here
toast("OK button clicked")
}
}
اصل تفکیک اینترفیسها – LSP
این اصل میگوید که هیچ کلاینتی نباید مجبور به استفاده از متدهایی باشد که نیاز ندارد.
این بدان معنی است که اگر کلاس A را بسازید و آن را در کلاس B پیادهسازی کنید، نباید همهی متدهای کلاس A را در کلاس B به اصطلاح، override کند. تا آسان و قابل درک باشد.
بیایید یک مثال بزنیم:
در داخل اکتیویتی، نیاز به پیادهسازی SearchView.OnQueryTextListener دارید ولی فقط نیاز به متد onQuerySubmit() دارید.
mSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
// Only need this method
return true
}
override fun onQueryTextChange(query: String?): Boolean {
// We don't need to implement this method
return false
}
})
چگونه این کار را انجام دهیم؟ ساده است شما باید فقط یک callback بسازید که SearchView.OnQueryTextListener را گسترش داده است.
interface SearchViewQueryTextCallback {
fun onQueryTextSubmit(query: String?)
}
class SearchViewQueryTextListener(val callback: SearchViewQueryTextCallback): SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
callback.onQueryTextSubmit(query)
return true
}
override fun onQueryTextChange(query: String?): Boolean {
return false
}
}
و در این جا نحوهی پیادهسازی view آمده است:
val listener = SearchViewQueryTextListener(
object : SearchViewQueryTextCallback {
override fun onQueryTextSubmit(query: String?) {
// Do the magic here
}
}
)
mSearchView.setOnQueryTextListener(listener)
و اگر از کاتلین استفاده میکنید میتوانید به صورت زیر آن را پیادهسازی کنید:
interface SearchViewQueryTextCallback {
fun onQueryTextSubmit(query: String?)
}
fun SearchView.setupQueryTextSubmit (callback: SearchViewQueryTextCallback) {
setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
callback.onQueryTextSubmit(query)
return true
}
override fun onQueryTextChange(query: String?): Boolean {
return false
}
})
}
و در آخر، به صورت زیر از آن استفاده کنید:
val listener = object : SearchViewQueryTextCallback {
override fun onQueryTextSubmit(query: String?) {
// Do the magic here
}
}
mSearchView.setupQueryTextSubmit(listener)
اصل وارونگی وابستگی – DIP
تعریف عمو باب از این اصل شامل دو نکته است:
- ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابستگی داشته باشند. هر دو باید به انتزاع وابستگی داشته باشند.
- انتزاعات نباید به جزئیات وابستگی داشته باشند. جزئیات باید به انتزاعات وابستگی داشته باشند
ماژولهای سطح بالا که منطق پیچیدهای را ارائه میدهند، باید با تغییر در ماژولهای سطح پایین که ویژگیهای کاربردی را ارائه میدهد، به راحتی قابل استفادهی مجدد و بیتاثیر باشند. برای رسیدن به این هدف شما باید انتزاعاتی را معرفی کنید که ماژولهای سطح بالا و پایین را از یکدیگر جدا کند.
یک مثال آسان برای آن در الگوی MVP است، شما یک شیء اینترفیس دارید که به شما کمک میکند با کلاسها ارتباط برقرار کنید. معنی آن چیست؟ کلاسهای Ui مانند اکتیویتی یا فرگمنت نیازی به دانستن متدهای پیادهسازی شده در presenter ندارند. بنابراین اگر هر تغییری داخل presenter ایجاد کنید، کلاسهای Ui نیازی به دانستن آن ندارند.
بیایید آن را در این مثال ببینیم:
interface UserActionListener {
fun getUserData()
}
class UserPresenter : UserActionListener() {
// .....
override fun getUserData() {
val userLoginData = gson.fromJson(session.getUserLogin(), DataLogin::class.java)
}
// .....
}
اکنون بیایید آن را در UserActivity ببینیم:
class UserActivity : AppCompatActivity() {
//.....
val presenter = UserPresenter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Activity doesn't need to know how presenter works
// for fetching data, it just know how to call the functions
// So, if you add method inside presenter, it won't break the UI.
// even the UI doesn't call the method.
presenter.getUserData()
}
//....
}
بنابراین آنچه ما انجام میدهیم این است که ما یک اینترفیس ایجاد میکنیم که انتزاعات presenter را ارائه میدهد و کلاس view اراجع به اینترفیس presenter را نگهداری میکند.
نتیجه
برنامهنویسان بالغ میدانند که این که همه چیز یک شیء است، افسانه است. گاهیاوقات شما فقط ساختار دادهای با عملیاتی که روی آنها انجام شده است را میخواهید. از این پس، شما باید به پیادهسازی فکر کنید و همچنین در مورد چشمانداز آینده که به آسانی آن را بروزرسانی کنید.
من میدانم که قبلا با نامگذاریهای مزخرف برنامهای را ایجاد کردهاید، کلاسهای بزرگ ایجاد کردهاید، کدهای درهم نوشتهاید، به من اعتماد کنید، من هم همین کارها را کردهام. به همین دلیل است که دانش خود را در مورد کد تمیز از عمو باب به اشتراک میگذارم. و این هم یادآوری برای من است و امیدوارم که توانسته باشم در درک آن به شما کمک کنم.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید