Temiz Kod Yazma Kültürü
--
Değişkenler
Bir proje geliştirirken mutlaka o projeyi sadece siz geliştiriyormuşsunuz gibi değil de, sanki sizden sonra başkası o projeyi devralacak ve o devam ettirecek diye düşünün. Hatta bu düşünce yapısında, sadece kendi kültürümüzdeki veya yetenek seviyemizde değil de, evrensel düşünmeliyiz.
Günümüzde projeler geliştirilirken artık Open Source kavramı ile bütün dünyanın ortak bir dil kültürü ile geliştirmiş olduğu yapılar kullanılmaktadır. İşin içine bütün dünya kültürleri girdiğinde, mutlak suretle belli bir düzen içine girmemiz gerekiyor. Sizin yazdığınız kodu bir başkası anlayamayabilir, hatta yanlış gelebilir. Başkasının kodunu da siz gördüğünüzde bu iş daha basit yapılır diye düşünebilirsiniz, ama aslında doğru olan o görmüş olduğunuz kod olabilir. Hatta bu kişi, o kodun doğru çalıştığını ispatlamak için de birim test (unit test) yazmış olabilir.
Biz de bu makale serimizde, tüm literatürü değil de en temelinden en azından olmazsa olmaz kurallarla yazılım geliştirme kültürümüzü belli bir seviyeye çıkarmaya çalışacağız. Ve tabi ki JavaScript dili ile çalışacağız.
Aklınızda bulunsun, bu makale serisinde öğrenecekleriniz sizi mükemmel bir yazılımcı yapmayacak, ancak o yolda ilerlemeniz için sizi belli bir seviyeye getirecek. Yıllar geçtikçe de daha çok kod yazdıkça, daha çok kod inceledikçe kendinizi daha da geliştireceksiniz.
Değişkenler
Değişken isimleri belirlerken hemen aklınıza gelen isimle değil, anlamlı ve belirgin bir isim kullanın.
Yanlış
const dateOfControl = new Date();
Doğru
const currentDate = new Date();
Aynı değişken türü için aynı kelimeleri kullanın.
Yanlış
getUserName();
getUserSurname();
Doğru
getUser();
Yazacağınız kodlar okunabilir ve aranabilir olmalıdır. Herhangi bir yerde bir parametre değeri kullanmanız gerekirse, onu doğrudan yazmayın, bir değişkene aktarın. Sonrasında burada yazan değer ne anlama geliyor diye düşünmek zorunda kalmazsınız.
Yanlış
// 60000 ne demek?
setTimeOut(anyMethod(), 60000);
Doğru
const ONE_MINUTE = 60000;
setTimeOut(anyMethod(), ONE_MINUTE);
Zihinsel haritalamalardan kaçının. Açıklayıcı olmak iyidir.
Yanlış
const cities = ["İstanbul", "Ankara", "İzmir"];
locations.forEach(l => {
getZipCode(1);
// 1 nedir?
});
Doğru
const cities = ["İstanbul", "Ankara", "İzmir"];
locations.forEach(city => {
getZipCode(city);
});
Gereksiz ifadelerden kaçının.
Yanlış
city.cityCode();
Doğru
city.code();
Kısa yollar ve koşullar kullanmayın. Bunun yerine varsayılan değer atamaları kullanın. Bu metot daha temizdir ve doğrudur. Varsayılan değer atamaları, sadece tanımsız, değer almamış ve işlevsiz değişkenlerde kullanılır. Yani ‘’, “”, null, NaN, undefined, 0, false gibi değerler döndüren parametrelere varsayılan atama yapılır.
Yanlış
const getName = name => {
return name || "İsim yok.";
}
Doğru
const getName = (name = “İsim yok.”) => {
return name;
}
Fonksiyonlar
Fonksiyonlarda parametre sayısı sınırlandırılmalıdır. Teoride bir fonksiyon canımızın istediği kadar parametre alabilir, ancak bu durum fonksiyonun hem anlaşılmasını, hem kullanılmasını hem de o fonksiyonun test edilebilirliğini imkansız hale getirir. En fazla 3 adet parametre alması uygundur, ideal olanı da 1 veya 2 parametredir. Eğer 3’ten fazla parametre alması gerekiyorsa, o fonksiyon parametre olarak Object almalıdır ve o Object içinden değerler ayrıştırılmalıdır. Ayrıştırma işlemini ES6’da gördüğümüz Object parçalama metodu ile rahatlıkla yapabiliriz.
Bir fonksiyon çok fazla parametre alıyorsa, çok fazla iş yapıyor demektir. Bu da yanlış bir durumun sinyalini verir, fonksiyonlar mümkün olduğunca az iş yapmaya odaklanmalıdır. Her fonksiyon kendi işini yapmalı, karmaşık işlemler yapmamalıdır.
Bir fonksiyona baktığımızda en fazla 3 saniyede o fonksiyonun ne iş yaptığını anlayamıyorsak, o fonksiyon tekrar elden geçirilmelidir.
Yanlış
const createMessage = (name, organization, city, time) => {
return "Merhaba" + name + ", " + organization + " şirketinden gelmiş olmalısınız. Saat şu an " + time + " ve şu an " + city + " şehrindeyiz.";
}
createMessage("X", "ABC", "İstanbul", "12:30");
Doğru
const createMessage = user => {
return "Merhaba" + user.name + ", " + user.organization + " şirketinden gelmiş olmalısınız. Saat şu an " + user.time + " ve şu an " + user.city + " şehrindeyiz.";
}const user = { name: "X", organization: "ABC", city: "İstanbul", time: "12:30" };
createMessage(user);
Fonksiyonlar sadece tek bir iş yapmalı. Bu kural, belki de yazılım geliştirirken dikkat edilmesi gereken en önemli kuraldır. Eğer bir fonksiyona birden fazla görev verirseniz, o fonksiyonun yönetilmesi zorlaşır, yazılım başka alanlarında da kullanılamaz. Bir süreci bir fonksiyona vermek yerine birkaç fonksiyona bölmeniz hem işinizi kolaylaştırır, hem de o fonksiyon parçacıklarını başka yerlerde de kullanabilmenize olanak sağlar. Ayrıca kodun daha temiz olmasını ve okunabilirliğini artırır.
Yanlış
showNameAndSurnameAndAge();
Doğru
showName();
showSurname();
showAge();
Fonksiyonların adları, onun ne iş yaptığını anlatmalıdır.
Yanlış
// Ne için gün ekleniyor?
addDay();
Doğru
addDateForPlan();
Yenilenen, yani tekrarlanan kodları kaldırın, sadeleştirin. Eğer bir kod birden fazla yerde tekrar ediyorsa, bir sorun var demektir. O tekrar eden kodlar, bağlı olduğu fonksiyonun aslında birden fazla iş yaptığını ve başka bir fonksiyonun da o işi tekrar yüklendiğini gösterir. Büyük bir sorun var ortada.
Tekrarlanan kodlarda bir yeri değiştirdiğinizde, diğer yerleri de değiştirmeniz gerekir.
Yanlış
const getMesage(message) => {
let currentTime = new Date().toLocaleTimeString();
return "Gelen mesaj " + message + "[" + currentTime + "]";
}const postMesage(message) => {
let currentTime = new Date().toLocaleTimeString();
return "Giden mesaj: " + message + "[" + currentTime + "]";
}
Doğru
const getCurrentTime = () => {
return new Date().toLocaleTimeString();
}const getMesage = message => {
return "Gelen mesaj " + message + "[" + getCurrentTime() + "]";
}const postMesage = message => {
return "Giden mesaj: " + message + "[" + getCurrentTime() + "]";
}
Object.assign ile varsayılan nesneleri ayarlayın.
Yanlış
const buttonConfig = {
text: "Google",
link: "https://www.google.com",
enabled: true
};const createButton = config => {
config.title = config.title || "Boş";
config.link= config.body || "#";
config.enabled = config.enabled !== undefined ? config.enabled : false;
}createButton(buttonConfig);
Doğru
const buttonConfig = {
text: "Google",
link: "https://www.google.com"
};const createButton = config => {
config = Object.assign(
{
title: "Boş",
link: "#",
enabled: false
},
config
);
}createButton(buttonConfig);
Yan etkilerden kaçınmak. Yani bir işi yaparken bir işi bozmak…
Fonksiyon, eğer parametreleri alıp işlem yapıp değer döndürmekten başka işler de yapıyorsa, yan etkilerinin olması kaçınılmazdır. Mesela fonksiyonunuz bir dosyaya yazı yazarken, işlem bitince yazdırılan değişkeni de siliyorsa bir sorun olabilir. Belki o değişken ve değeri kullanılacaktı. Bu tür yan etkiler kaçınılmaz olabilir, ancak her bir fonksiyonu yeteri kadar merkezileştirebilirseniz, yan etkilerle baş etmeniz daha kolay olacaktır.
Yanlış
let message = "Bu bir deneme yazısıdır.";const countMessageWords = text => {
message = text.split(" ");
return message.length;
console.log(message);
// Yan etki oldu, mesaj değişkeni artık 4 sonucunu veriyor.
}countMessageWords(message);
Doğru
let message = "Bu bir deneme yazısıdır.";const countMessageWords = text => {
return text.split(" ");
}const messageWordLength = countMessageWords(message).length;console.log(messageWordLength);
Diğer bir yan etki durumu da nesne, dizi gibi veri türleri ile çalışırken yaşanılan veri kaybı durumu olabilir. Mesela bir diziye yeni eleman eklemek istediğinizde, o an bellekte yaşanabilecek bir sorun ile tüm veriyi kaybedebilirsiniz. Bu gibi durumlarda nesneyi klonlayarak işlem yapmak gerekebilir. Klonlamak, bellekte fazlaca yer kaplayan bir durumdur, bu nedenle aşırı kullanılmaması gerekir.
Yanlış
const addItemToList = (list, item) => {
list.push({ item, date: Date.now() });
};
Doğru
const addItemToList = (list, item) => {
return [...list, { item, date: Date.now() }];
};
Global fonksiyonlar yazmayın. Globali gereksiz kodlarla kirletmek kötü bir yöntemdir. Çünkü başka kütüphanelerle veya API’lar ile çakışmanız olasıdır. Mesela JavaScript’te Array nesnesini, iki dizi arasındaki farkı gösteren bir different metodu ile genişletmek istediğinizi düşünün. Bunu Array.prototype ile yazabiliriz, ancak projeye dahil ettiğimiz başka bir framework veya library’de aynı isimle metot oluşturmuşsa çakışma yaşanacaktır. Prototipleme yerine istenilen nesneyi extend edip Class oluşturmak ve içinde metotları tanımlamak daha doğrudur.
Yanlış
Array.prototype.different = function different (comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};const totalList = [1,2,3,4,5];
const checkList = [2,5]console.log(totalList.different(checkList)); //[1, 3, 4]
Doğru
class ExtendedArray extends Array {
different(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}
Fonksiyonel programlama yönelimini tercih edin. Fonksiyonel programlamanın test edilebilirliği daha kolaydır.
Yanlış
const numbers = [1,5,6,7];let totals = 0;for (let i = 0; i < numbers.length; i++) {
totals += numbers[i];
}console.log(totals); //19
Doğru
const numbers = [1,5,6,7];const totals = numbers.reduce(
(currentTotal, output) => currentTotal + output, 0
);console.log(totals); //19
Koşulların kapsamasında tüm koşulları ve mantıksal değer döndüren fonksiyonları koşullarda kullanmayın.
Yanlış
if (data.state === "empty" && isActive(data)) {
// ...
}
Doğru
function shouldGenerateCard(data, user) {
return data.state === "empty" && isActive(user);
}if (shouldGenerateCard(dataInstance, userInstance)) {
// ...
}
Olumsuz koşullardan kaçının.
Yanlış
if(!user.userActive()){
//
}
Doğru
if(user.userActive() === true){
//
}
Koşulların kendisinden de kaçının. Her bir koşul, sistemi yoran, ayrıca kodu kirleten bir işlemdir. Sürekli olarak koşullarla uğraşmanız kaçınılmaz olabilir. İmkansız gibi görünüyor ama bazen gerçekten de koşul kullanılması gerekiyor mu diye sorgulamanızda fayda var.
Yanlış
const calculate = (type, numberOne, numberTwo) => {
if(type === "sum"){
return numberOne + numberTwo;
}
if(type === "diff"){
return numberOne - numberTwo;
}
}
Doğru
const getDiff = (numberOne, numberTwo) => numberOne - numberTwo
const getSum = (numberOne, numberTwo) => numberOne + numberTwogetDiff(1, 5); //-4
getSum(2, 5); //7
Gereksiz yazım denetiminden kaçının. Eğer kompleks verilerle çalışmıyorsanız yazım denetimi yapmak pek de gerekli değildir. Eğer gerçekten ihtiyacınız varsa, bu durumda TypeScript’e yönelmenizin vakti gelmiş demektir. JavaScript’te mümkün olduğunca tür denetimine ihtiyacınız olmayacak şekilde temiz, iyi okunabilir ve test edilebilir kodlar yazın.
Yanlış
const combine = ( a,b ) = {
if (
(typeof a === "number" && typeof b === "number") ||
(typeof a === "string" && typeof b === "string")
) {
return a + b;
}
throw new Error("Parametre türleri aynı değil.");
}
Doğru
const combine = ( a,b ) = {
return a + b;
}
Aşırı optimizasyon yapmaktan kaçının. Zaten modern tarayıcılar kodunuzu yeterince optimize edecektir. Eğer gerçekten de optimizasyon işini tarayıcının kendisinden daha iyi yapabileceğinizden emin değilseniz, optimizasyon işine pek bulaşmayın. Bu hem vakit kaybıdır, hem de kodunuzu kirletir.
Yanlış
// len değişkenine gereksiz, tarayıcı zaten durumu algılayacaktır.
for (let i = 0, len = user.length; i < len; i++) {
// ...
}
Doğru
for (let i = 0; i < user.length; i++) {
// ...
}
Ve fonksiyonlarla ilgili son olarak; kullanılmayan fonksiyonları silin. İlerde belki lazım olur, belki bir daha bu metodu yazmamam kalsın mantığıyla gereksiz kodları projede tutmayın.
Açıklamalar
Sadece işleyiş mantığı karmaşık olan şeyleri yorumlayın. Yorumlar zorunluluk değil veya bir şeyi açıklamak da değildir. İyi bir kod kendisini zaten açıklar.
Yanlış
// Sayılar dizide tutuluyor.
var numbers = [1,5,6,7];
// Her bir sayı tek tek toplanıp totals değişkenine aktarılıyor.
const totals = numbers.reduce(
(currentTotal, output) => currentTotal + output, 0
);
// Toplam değerler konsolda yazdırılıyor.
console.log(totals); //19
Doğru
var numbers = [1,5,6,7];
// Bütün sayıların toplamı
const totals = numbers.reduce(
(currentTotal, output) => currentTotal + output, 0
);
console.log(totals);
Kullanılmayan metotları yorum satırlarında bırakmayın.
Yanlış
getUserName();
//getUserNameFromData();
Doğru
getUserName();
Yorumlarda satırlarca açıklama eklemeyin. Eğer kodlarınızın takibini yapmak istiyorsanız herhangi bir versiyonlama sistemi kullanın ve smart-tag sistemi ile her bir işleminizi versiyonlayın. Yani uzun uzun açıklamalarınızı versiyonlarken yazın.
Yanlış
/* Kullanıcı adını çeken fonksiyon yazdık */
/* Bu fonksiyon önce data gelmiş mi diye bakar */
/* Data varsa içinden kullanıcı adını çeker */
getUserName();
Doğru
getUserName();
Konum belirten garip garip açıklama satırlarından kaçının. Genelde kodun içinde kaybolmuş kişiler gereğinden fazla uzamış kodun içinde aradığı önemli fonksiyonu bulmak için bazı desenler çizer.
Yanlış
////////////////////// VERİYİ ÇEKEN METOT BURADA //////////////////////
getData();
Doğru
getUserName();
Biçimlendirmeler
Biçimlendirmeler, programlama dillerinde zorunlu olmayan, ancak kodun okunabilirliği açısından zorunluluğu kaçınılmaz olan kod düzen sistemidir. Mesela kodlarda yer alan girintiler, sekmeler, boşluklar, çift veya tek tırnaklar gibi… Bunun için IDE’lerde birçok yerleşik veya ekstradan yüklenen plug-in’lerle kodlar biçimlendirilebilir, bunlardan en uygun olanını kullanın. Mesela kitapta Visual Studio Code’da yerleşik olarak Format Document veya Format Selection araçları kullanılabilirken, ekstra plug-in’lerden js-beautify da kullanılabilir.
Yanlış
const pi = 3.14
// Daire alanı hesapla
const calculateCirclearea = r =>{
return r*r *pi}
Doğru
const PI = 3.14;
// Daire alanı hesapla
const calculateCircleArea = r => {
return r * r * PI
}
Büyük/küçük harfleri ve özel karakterleri tutarlı kullanın.
Yanlış
const MONTH_IN_WEEK = 7;
const daysInyear = 365;
const books = ["Kitap 1", "Kitap 2"];
const Authors = ["Yazar 1", "Yazar 2"];
function getBooks() {}
function get_Authors() {}
class bookStore {}
class author {}
Doğru
const MONTH_IN_WEEK = 7;
const DAYS_IN_YEAR = 365;
const BOOKS = ["Kitap 1", "Kitap 2"];
const AUTHORS = ["Yazar 1", "Yazar 2"];
function getBooks() {}
function getAuthors() {}
class BookStore {}
class Author {}
Tanımlı olan bir fonksiyon başka bir yerden çağırılıyorsa, çağıran ile çağrılan birbirine yakın olsun. Doğal okuma düzeni yukarıdan aşağıya doğrudur. Bu nedenle çağırılan yeri fonksiyonun üzerinde tutmak idealdir. Ancak bazı framework’lerde yukarıda çağırdığınızda, fonksiyon henüz tanımlı olmadığı için (henüz fonksiyonun olduğu satıra gelinmedi), tanımsız fonksiyon gibi hatalar alabilirsiniz. Bu gibi durumlarda da fonksiyonların alt tarafında kullanabilirsiniz.
Yanlış
class Books {
constructor() {
} func_1() {
//
} func_3() {
//
} func_2() {
//
} define () {
this.func_1();
this.func_2();
this.func_3();
}}
const book = new Books ();
book.define();
Doğru
class Books {
constructor() {
} define() {
this.func_1();
this.func_2();
this.func_3();
} func_1() {
//
} func_2() {
//
} func_3() {
//
}}
const book = new Books ();
book.define();