# sudo vim /etc/default/rcS
Change from:
TMPTIME=0
To:
TMPTIME=1
Taner Diler's Blog
9 Ocak 2012 Pazartesi
19 Aralık 2011 Pazartesi
Ubuntu üzerinde Ruby On Rails kurulumu yapmak
Ubuntu 10.04 üzerinde Ruby kurulumu gerçekleştireceğiz.
sudo apt-get install aptitude
sudo aptitude update
sudo aptitude install build-essential git-core curl
bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"' >> ~/.bashrc
. ~/.bashrc
rvm requirements
/usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
sudo /usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
rvm install 1.9.2
rvm use 1.9.2
ruby -v
rvm --default use 1.9.2
$rvm gemset create YOUR_GEMSET_NAME
$rvm 1.9.2@YOUR_GEMSET_NAME
$gem install bundler
$ gem install rails --no-rdoc --no-ri
$ rails new RAIL_APP
Edit Gemfile
add/edit this group definition
group :development, :test do
gem 'rspec-rails'
end
$ bundle install
$ sudo apt-get update
# Install needed packages
$ sudo apt-get install git-core curl build-essential openssl libssl-dev
# Install node.js
$ git clone https://github.com/joyent/node.git && cd ~/Development/NodeJS
$ ./configure
$ make
$ sudo make install
$ cd
# Install npm
$ curl http://npmjs.org/install.sh | sudo sh
#Install CoffeeScript
$ sudo npm install -g coffee-script
$ sudo apt-get install libmysql-ruby libmysqlclient-dev
#change gem 'sqlite3' to gem 'mysql2' in Gemfile
$ bunlder install
$ rake db:create
#make slim as a default template add this "Slim::Engine.set_default_options :sections => true" line to config/environment.rb
Create a migration file of exist schema, run this command; this command generates a file in db/schema. Last command reload schema, you can see the effect on schema_migrations table.
$ rake db:schema:dump RAILS_ENV=development
$ rake db:schema:load
sudo apt-get install aptitude
sudo aptitude update
sudo aptitude install build-essential git-core curl
bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"' >> ~/.bashrc
. ~/.bashrc
rvm requirements
/usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
sudo /usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
rvm install 1.9.2
rvm use 1.9.2
ruby -v
rvm --default use 1.9.2
$rvm gemset create YOUR_GEMSET_NAME
$rvm 1.9.2@YOUR_GEMSET_NAME
$gem install bundler
$ gem install rails --no-rdoc --no-ri
$ rails new RAIL_APP
Edit Gemfile
add/edit this group definition
group :development, :test do
gem 'rspec-rails'
end
$ bundle install
$ sudo apt-get update
# Install needed packages
$ sudo apt-get install git-core curl build-essential openssl libssl-dev
# Install node.js
$ git clone https://github.com/joyent/node.git && cd ~/Development/NodeJS
$ ./configure
$ make
$ sudo make install
$ cd
# Install npm
$ curl http://npmjs.org/install.sh | sudo sh
#Install CoffeeScript
$ sudo npm install -g coffee-script
$ sudo apt-get install libmysql-ruby libmysqlclient-dev
#change gem 'sqlite3' to gem 'mysql2' in Gemfile
$ bunlder install
$ rake db:create
#make slim as a default template add this "Slim::Engine.set_default_options :sections => true" line to config/environment.rb
Create a migration file of exist schema, run this command; this command generates a file in db/schema. Last command reload schema, you can see the effect on schema_migrations table.
$ rake db:schema:dump RAILS_ENV=development
$ rake db:schema:load
9 Aralık 2011 Cuma
Kanban Ruhu
Şu sıralar "Kanban - Successful Evolutionary for Your Tech Business" kitabını okuyorum. Kanban kavramına şu an çalıştığım yerde ilgili olmaya başladım ama aslında bu sistemi daha önce çalıştığım yerde ruhumuzda oluşturmuşuz zaten. Scrum süreç yönetimini uygulayan bir şirketin ufak parçalara ayrılmasından sonra adı Scrum olmayan ama yime Project Owner, Tester, Developer birimlerinden - her biri bir/iki kişiden - oluşan bir süreç oluşturmuştuk kendimize. Developer yazar, Tester test eder, Project Owner müşteri ile - müşteri parçalanma sonrasında iş veren oldu - konuşur ve iş birimlerini yönetirdi.
Developer ekibi iki kişiden oluşmaktaydı. Bunlardan biri de bendim. İki kişi de farklı projelerden sorumluydu. Lakin şöyle bir sıkıntımız mevcuttu. İki farklı proje ama aynı context altında hizmet vermekte ve aynı trunk üzerinde mergeler geliştirilmekteydi. Bir hikaye tamamlanır tamamlanmaz deployment gerçekleşirdi ama deployment süreci branch'lerin merge edilmesi, merge'den kaynaklanan sorunların tespiti v.b. yaklaşık bir günümü alıyordu. Haftada birkaç kez releaase'lerin çıkması ciddi şekilde iş kaybına neden oluyordu. Bunu çözmemiz gerekiyordu ve bunun için cuma günlerini deployment günü ilan ettik. Her hafta cuma günü düzenli bir şekilde deployment'i gerçekleştiriyorduk.
Bir sonraki haftanın ilk günü ise geçen hafta yapılan çalışmanın özeti ve yapılacakların tartışıldığı bir toplantı yapıyorduk. Bu toplantıda tasarımcı, tester, yazılımcı, project owner ve müşteri bulunuyordu. Böylelikle müşterinin sürekli güncel olması sağlanıyordu.Bu toplantıda o hafta backlog'daki listemizden maaliyetine göre nelerin yapılacağı veya yeni hangi özelliklerin eklenmesi gerektiği tartışılıyordu.
Mevcut sistem içerisinde değişimi yine kendimiz gerçekleştirmiş ve yönetmiş olduk. Kanban mevcut sistemdeki sıkıntıları görmek ve direnci min. seviyede tutarak değişim uygulamak için ideal bir sistem. Bir ritim anlayışını oluşturmakta ve WIP'e limitler koyarak sıkıntıları yönetilebilir hale getirmektedir.
Developer ekibi iki kişiden oluşmaktaydı. Bunlardan biri de bendim. İki kişi de farklı projelerden sorumluydu. Lakin şöyle bir sıkıntımız mevcuttu. İki farklı proje ama aynı context altında hizmet vermekte ve aynı trunk üzerinde mergeler geliştirilmekteydi. Bir hikaye tamamlanır tamamlanmaz deployment gerçekleşirdi ama deployment süreci branch'lerin merge edilmesi, merge'den kaynaklanan sorunların tespiti v.b. yaklaşık bir günümü alıyordu. Haftada birkaç kez releaase'lerin çıkması ciddi şekilde iş kaybına neden oluyordu. Bunu çözmemiz gerekiyordu ve bunun için cuma günlerini deployment günü ilan ettik. Her hafta cuma günü düzenli bir şekilde deployment'i gerçekleştiriyorduk.
Bir sonraki haftanın ilk günü ise geçen hafta yapılan çalışmanın özeti ve yapılacakların tartışıldığı bir toplantı yapıyorduk. Bu toplantıda tasarımcı, tester, yazılımcı, project owner ve müşteri bulunuyordu. Böylelikle müşterinin sürekli güncel olması sağlanıyordu.Bu toplantıda o hafta backlog'daki listemizden maaliyetine göre nelerin yapılacağı veya yeni hangi özelliklerin eklenmesi gerektiği tartışılıyordu.
Mevcut sistem içerisinde değişimi yine kendimiz gerçekleştirmiş ve yönetmiş olduk. Kanban mevcut sistemdeki sıkıntıları görmek ve direnci min. seviyede tutarak değişim uygulamak için ideal bir sistem. Bir ritim anlayışını oluşturmakta ve WIP'e limitler koyarak sıkıntıları yönetilebilir hale getirmektedir.
14 Ağustos 2011 Pazar
Sending Basecamp Worklogs to Campfire with Ruby
We collects all system notifications at Campfire Chat Room. We decide to send worklogs entered to Campfire. I wrote the code under. I choosed Ruby Programming Language for fast development.
#!/usr/bin/ruby
require 'rubygems'
require 'rexml/document'
require 'date'
include REXML
$_37SIGNAL_ACCOUNT="YOUR 37 SIGNALS ACCOUNT_NAME"
$CAMPFIRE_TOKENACCESS_KEY="YOUR CAMPFIRE ACCESS TOKEN"
$CAMPFIRE_ROOM_ID="YOUR ROOM ID"
$BASECAMP_TOKENACCESS_KEY="YOUR BASECAMP ACCESS TOKEN"
class TimeEntryIdFile
def self.read
lastWorkLogId = 0
if File.exist? "/tmp/last.worklog.id" then
File.open('/tmp/last.worklog.id', 'r') do |f1|
while line = f1.gets
lastWorkLogId = line
end
end
return lastWorkLogId
end
end
def self.update(lastId)
File.open('/tmp/last.worklog.id', 'w').write(lastId)
end
end
class Campfire
def self.sendMessage(message)
message = message.gsub("'", "-")
`curl -u #{$CAMPFIRE_TOKENACCESS_KEY}:X -H 'Content-Type: application/json' -d '{\"message\":{\"body\":\"#{message}\"}}' https://#{$_37SIGNAL_ACCOUNT}.campfirenow.com/room/#{$CAMPFIRE_ROOM_ID}/speak.json`
end
end
class BaseCamp
BASECAMP_API_COMMAND="curl -k -u #{$BASECAMP_TOKENACCESS_KEY}:X -H 'Accept: application/xml' -H 'Content-Type: application/xml' https://#{$_37SIGNAL_ACCOUNT}.basecamphq.com"
def self.project(projectId)
projectDoc = `#{BASECAMP_API_COMMAND}/projects/#{projectId}.xml`
doc = Document.new(projectDoc)
return Project.parse(doc.root)
end
def self.person(personId)
personDoc = `#{BASECAMP_API_COMMAND}/people/#{personId}.xml`
doc = Document.new(personDoc)
return Person.parse(doc.root)
end
def self.todo(todoItemId)
todoDoc = `#{BASECAMP_API_COMMAND}/todo_items/#{todoItemId}.xml`
doc = Document.new(todoDoc)
return ToDo.parse(doc.root)
end
def self.todoList(todoListId)
todoListDoc = `#{BASECAMP_API_COMMAND}/todo_lists/#{todoListId}.xml`
doc = Document.new(todoListDoc)
return ToDoList.parse(doc.root)
end
def self.timeEntries ()
timeXmlDoc = `#{BASECAMP_API_COMMAND}/time_entries/report.xml`
doc = Document.new(timeXmlDoc)
timeEntries = Array.new
doc.root.each_element('//time-entry') {|timeEntryElement| timeEntries.push(TimeEntry.parse(timeEntryElement)) }
return timeEntries
end
def self.timeEntriesFrom(lastTimeEntryId)
puts "last time entry id #{lastTimeEntryId}"
lastTimeEntries = Array.new
timeEntries = BaseCamp.timeEntries()
timeEntries.each {|timeEntry|
if lastTimeEntryId.nil? or lastTimeEntryId == 0 or timeEntry.id > lastTimeEntryId then lastTimeEntries.push(timeEntry) end
}
return lastTimeEntries
end
end
class ToDoList
def initialize(name)
@name=name
end
def self.parse(xmlElement)
return ToDoList.new(xmlElement.elements["name"].text)
end
def name()
return @name
end
end
class ToDo
def initialize(content, listId)
@content=content
@listId=listId
end
def self.parse(xmlElement)
return ToDo.new(xmlElement.elements["content"].text, xmlElement.elements["todo-list-id"].text)
end
def content()
return @content
end
def listId()
return @listId
end
end
class Project
def initialize(name)
@name=name
end
def self.parse(xmlElement)
return Project.new(xmlElement.elements["name"].text)
end
def name()
return @name
end
end
class Person
def initialize(firstName, lastName)
@firstName=firstName
@lastName=lastName
end
def self.parse(xmlElement)
return Person.new(xmlElement.elements["first-name"].text, xmlElement.elements["last-name"].text)
end
def name()
return @firstName +" "+@lastName
end
end
class TimeEntry
def initialize(id, personId, projectId, todoItemId, hours, desc, date)
@id=id
@personId = personId
@projectId = projectId
@todoItemId = todoItemId
@hours = hours.to_f
@desc=desc
@date = date
end
def self.parse ( xmlElement )
timeEntry = TimeEntry.new(
xmlElement.elements["id"].text,
xmlElement.elements["person-id"].text,
xmlElement.elements["project-id"].text,
xmlElement.elements["todo-item-id"].text,
xmlElement.elements["hours"].text,
xmlElement.elements["description"].text,
xmlElement.elements["date"].text)
return timeEntry
end
def id
return @id
end
def formattedDate()
return Date.strptime(@date, '%Y-%m-%d').strftime("%Y, %b %d")
end
def hours
if @hours == 0.5 then
return "yarim"
elsif (@hours -@hours.floor) == 0.5 then
return @hours.floor.to_s + " bucuk"
else
return @hours
end
end
def message
person = BaseCamp.person(@personId)
project = BaseCamp.project(@projectId)
todo = BaseCamp.todo(@todoItemId)
todoList = BaseCamp.todoList(todo.listId)
message = "\\n\\nToDo List Name : " +todoList.name
message += "\\n\\n@WorkLog "+self.formattedDate()+"\\n\\n"+person.name+",\\n\\n"
if @hours.to_f == 0.0 then
message += "hic ugrasmamis bir de worklog giriyor"
else
message += "'"+todo.content+"' isiyle "+self.hours().to_s+" saat ugrasmis"
end
unless @desc.nil? then
message += ",\\n\\ndedi ki:\\n\\n"+@desc
end
return message
end
end
lastWorklogID=0
lastWorklogID = TimeEntryIdFile.read()
lastTimeEntries = BaseCamp.timeEntriesFrom(lastWorklogID)
if lastTimeEntries.length !=0 then
campfireMessage = ""
lastTimeEntries.each {|timeEntry| campfireMessage = campfireMessage+"\\n"+timeEntry.message()}
Campfire.sendMessage(campfireMessage)
TimeEntryIdFile.update(lastTimeEntries[0].id)
end
30 Temmuz 2011 Cumartesi
Temiz Kod Yazımı : Anlamlı İsimlendirmeler
Kod yazarken oluşturduğumuz herşeye isim vermeye çalışırız. Bazen saatlerce düşünürüz acaba ne isim versem diye. Çünkü aynı ismi başka bir yerde kullanmışızdır. Yeni vereceğimiz isim bir öncekinin eklerler veya tamlamalarla çeşitlendirilmiş hali olabilir. Ama bütün gaye yeni olanı diğerinden ayıran anlamı birkaç kelime ile verebilmektir.
Kod yazan kişi için verilen isim iç açıcı olabilir ama yukardan yani müşteri seviyesinde baktığımız da ne kadar iç açıcı olabilir! "Ben bunu anlamadım." şeklinde bir yaklaşımda bulunulduğunda yazmış olduğunuz eseri taşlıyorlarmış gibi hissedersiniz. Empati kurmaya başladığınızda anlarsınız ki evet gerçtekten hiçbir şey anlaşılmıyor. Artık Business seviyesinde yani patron, müşteri seviyesinde düşünüp ben burada bu değeri verebilmiş miyim diye tekrardan sormamız gerekiyor kendimize. Self-CodeReviewing diyebiliriz belki buna.
Domain Driven Design bize business seviyesinde modelleme yapmamıza olanak sağlar. Wikipedia'da bakın nasıl tanımlanmış "Domain-driven design (DDD) is an approach to developing software for complex needs by deeply connecting the implementation to an evolving model of the core business concepts."
InfoQ'da ki "Domain Driven Design and Development in Practice" isimli bir makale de ise "Domain Driven Design (DDD) is about mapping business domain concepts into software artifacts." olarak tanımlanmış.
Amacı Açıklayan İsimlendirmeler (Intention Revealing Names)
Yapılan isimlendirmeler şu soruya cevap vermeli "Bu ne için var, Ne yapıyor ve Nasıl kullanılmış". Eğer bir isimlendirmeye yönelik bir yorum/açıklama yazılmışsa koda, bilin ki o isimlendirme amacını açıklamıyor.
Mesela:
List<ogrenci> ogrenciler; // devamsızlığı olan öğrenciler
ogrenciler isimlendirmesi yukarıdaki sorularımızın hiçbirine cevap vermiyor.
Aşağıdaki gibi bir isimlendirme kullansaydık sorularımıza cevap verebilirdik ve yorum yazmaya da gerek kalmazdı.
List<ogrenci> devamsizligiBulunanOgrenciler;
Peki aşağıdaki kod ne iş yapmaktadır?
public List<int> icerisindeBul(List<int[]> kaynakListe){
List<int> liste1 = new ArrayList<int[]>;
for ( int[] oge : kaynakListe) {
if ( oge[1] == 2 ) {
liste1.add(oge[0]);
}
}
return liste1;
}
Ne yaptığı anlaşılmakta gibi ama eğer bunu bir utility method olarak tanımlarsak. Bunun dışında bir listeden 2. elemanı 2 olan dizinin 1. elemanını başka bir dizi olarak geri döndürüyoruz. Ama neden???
Soralım:
1. kaynakListe içerisindeki tutulan değerler neyi ifade ediyor?
2. kaynakListe içerisindeki dizilerin 2. elemanının ne gibi bir özelliği var?
3. karşılaştırma 2 değeri neyi ifade ediyor?
4. Döndürülen listeyi ben nasıl kullanacağım?
Bu soruların hiçbirine cevap veremiyoruz değil mi?
Kodumuzu şöyle yazsaydık:
public List<int> devamsizlikYapanOgrencilerinNolariniGetir(List<int[]> kayitliTumOgrenciler){
List<int> ogrenciNolari = new ArrayList<int[]>;
for ( int[] ogrenci : kayitliTumOgrenciler) {
if ( devamsizlikYapanOgrenciMi (ogrenci) ) {
ogrenciNolari .add(oge[0]);
}
}
return ogrenciNolari ;
}
şimdi tüm sorularımıza cevap verebiliyoruz. Ayrıca "oge[1] == 2" karşılaştırmasını yardımcı bir methoda taşıyarak bu kodun 100% anlamlı olmasını sağladık.
Yanlış Bilgiden Kaçınılmalı
Bazen yazmış olduğumuz platform ve teknolojiye ait kodlarımızın içerisinde o an anlamlı olabilecek ama başka bir zamanda bize bile anlamsız gelecek ifadeler kullanabiliriz. Bir Unix platformu için yapacağımız kod çalışmasında scp, ssh gibi; JDBC kodlaması yapıyorsak ResultSet'in kısaltılmış hali olan rs gibi kısaltmalar kullanmak mantıklı olabilir ama yine de anlamsızdırlar.
Bazen verilen ifadelerin sonlarına - özellikle bu Collection yapılarında çok yapılıyor - veri tipi ekleniyor; ogrenciList, nameStr gibi... ogrenciList yerine ogrenciler ifadesinin kullanımı anlamlı olacaktır. ogrenciList olarak ifade edilen bir yapı gerçekte List tipinde olmayabilir. OgrenciList bir sınıf tipi de olabilir. Burada gereksiz eklentiler yanlış bilgilendirmeye neden olmaktadır.
İsimlendirmelerde Anlamlı Farklılıklar Oluşturun
Kullandığımız dilin getirdiği kurallar nedeni ile isimlendirmeleri çeşitlendirmemiz gerekiyor. Aynı alanda (scope) aynı değişken ismini farklı tipler için birkaç kez kullanamıyoruz. Sırf derleyiciyi memnun etmek için bu sefer eklerle veya karakterle ayrımı yapmak zorunda kalıyoruz.
Derleyiciyi memnun etmek için neler yapıyoruz:
Değişken isminin başına veya sonuna "_" karakteri koyuyoruz. Eğer aynı isimden çok kullanmamız gerekirse bakın ne hale gelecek : "araba_______" :D
Söylenebilir isimler kullanmak retrospektif toplantılarında R2D2 gibi sesler çıkarmamızı engeller. Mesela arayüz kodlarında çok kullanılan wysiwyg kısaltmasını kullanırsak bunu söylerken nasıl bir duruma düşeceğimizi hayal edebilirsiniz.
Aranılabilir Kelimeler Kullanın
Bundan önce bahsettiğimiz kurallar bu kurala zaten uymamıza neden olmaktadır. Kısaltmalar, anlamsız isimlendirmeler bizi aranılabilir bir içerikten uzaklaştıracaktır.
Arayüz (Interface) ve Gerçekleştirici (Implementer) İsimlendirmeleri
Arayüz ile gerçekleştirici arasındaki ayrımı nasıl yaparsınız? Sizin için önemli olan arayüz mü yoksa gerçekleştirici mi?
Kimileri arayüzleri gerçekleştiricilerinden şu şekilde ayırır "IHesapMakinasi" kimileri de gerçekleştiriciyi arayüzden şu şekilde ayırır "HesapMakinasiImp". Peki hangisini seçmeli?
Birbiri ile network üzerinen konuşan iki sistem yaptığınızı düşünün. Nesneleri serialize edip network üzerinden gönderelim. Gelen byte kodları deserialize yapıldığında elimizde bir nesne olacaktır ama bu nesne hangi tipte olduğunu alıcı JVM bilmiyor. Bunun için bir tanımlayıcı gerekmektedir. Siz arayüz tanımını alıcı tarafa verdiyseniz kolaylıkla bunu gerçek tipine dönüştürebilir. Bu durumda bizim için önemli olan arayüz (interface). Arayüze gerçek bir isim verirsem onun gerçekleştiricilerinin ne olduğu önemsiz kalır. Bu durumda arayüzü "HesapMakinasi" olarak adlandırmamız gerekecek. Gerçekleştiriciyi isterseniz "HesapMakinasiImpl" veya "BasitHesapMakinasi" / "MuhendisHesapMakinasi" / "GelismisHesapMakinasi" olarak adlandırabilirsiniz.
Editörlerde dosyaların isimlerine göre sıralandığını düşünürsek "HesapMakinasi" ve "HesapMakinasiImpl" hep alt alta olacaktır.
Fantastik Kelimelerden Kaçının
Hiç unutmuyorum performans analizi yaptığımız bir uygulamayı yazarken ileriye yönelik tahmin modülü için FortuneTeller ismini koymuştum. "Clean Code" kitabını okuduktan sonra yanlış bir kullanım yaptığımın farkına vardım.
Her Amaç İçin Tek Kelime
Projeye başlamadan önce kendi standartlarınızı belirleyin. Bu standartlar içerisinde kullanacağınız genel kelimelerde bulunmalıdır. Örneğin fetch, get, retrieve kelimelerinin hepsi aynı anlama gelmektedir. Bir standartınız yoksa hangi methodun ne için çağrılacağı ve oluşturulacağı bilinemez. get methodunu bir sınıfın özelliklerine erişmek için kullanabilirsiniz bu bir JEE standartı ve birçok editör, 3th party API'lar bu standarta uyulduğunu varsaymaktadır.
Controller, Manager gibi kelimeler daha çok yapılar arasındaki iletişimi yönetmektedirler. Manager, Controller kelimesi gördüğünüzde orada mutlaka genel birşeyler yapılmaktadır ve mutlaka içerisi yönetilemeyece şekilde şişmiştir. Bu kelimelerin kullanımı bir antipattern'dir.
Eğer iki değeri birleştirip yeni bir nesne oluşturan bir methodunuz varsa bu method'a "add" ismini koyabilirsiniz. Aynı sistemde bir değeri bir diziye ekleme işlemi için "add" kullanamazsınız. "append" veya "insert" daha anlamlı bir kullanım olacaktır.
Temiz Kod Yazımı Nasıl Olmalı?
Şu sıralar Robert C. Martin tarafından yazılmış "Clean Code kitabını okuyorum. Kitaba temiz kodun ne anlama geldiğini ünlü kodculara sorarak başlamış.
- Bjarne Stroustrup (C++'ı geliştiren ve "The C++ Programming" kitabının yazarı) : Temiz kod birşeyi en iyi şekilde yapmalıdır. Hata yönetimi ve mimarisi etkili, kolay geliştirilebilir ve performansı yüksek olmalıdır.
- Grady Booch, "Object Oriented Analysis and Design with Applications" kitabının yazarı : Temiz kod basit ve ne yaptığını direkt göstermelidir. İyi yazılmış bir yazı gibi okunabilmelidir.
- "Big" Dave Thomas, OTI'nin yaratıcısı ve Eclipse stratejisinin babası: Temiz kodu okunabilir ve orijinal kodcusundan başka bir kodcu tarafından geliştirilebilir olmalıdır. Bir şeyi yapmak için birden fazla yol yerine tek bir çözüm yolunu sunmalıdır.
Söylemlerden de görüldüğü gibi temiz kod; basit, okunabilen ve tek iş yapandır. Kod yazarken şu soruyu sorabilirsiniz kendinize "Yazılımla ilgisi olmayan biri bu kodu okduğunda ne yaptığını anlayabilir mi?". Eğer hayır ise demekki karmaşık ve okunamayan kod yazıyorsunuz demektir.
- Bjarne Stroustrup (C++'ı geliştiren ve "The C++ Programming" kitabının yazarı) : Temiz kod birşeyi en iyi şekilde yapmalıdır. Hata yönetimi ve mimarisi etkili, kolay geliştirilebilir ve performansı yüksek olmalıdır.
- Grady Booch, "Object Oriented Analysis and Design with Applications" kitabının yazarı : Temiz kod basit ve ne yaptığını direkt göstermelidir. İyi yazılmış bir yazı gibi okunabilmelidir.
- "Big" Dave Thomas, OTI'nin yaratıcısı ve Eclipse stratejisinin babası: Temiz kodu okunabilir ve orijinal kodcusundan başka bir kodcu tarafından geliştirilebilir olmalıdır. Bir şeyi yapmak için birden fazla yol yerine tek bir çözüm yolunu sunmalıdır.
Söylemlerden de görüldüğü gibi temiz kod; basit, okunabilen ve tek iş yapandır. Kod yazarken şu soruyu sorabilirsiniz kendinize "Yazılımla ilgisi olmayan biri bu kodu okduğunda ne yaptığını anlayabilir mi?". Eğer hayır ise demekki karmaşık ve okunamayan kod yazıyorsunuz demektir.
28 Temmuz 2011 Perşembe
Web Uygulaması İçin Otomatik Kabul Testi Yazımı
Otomatik Kabul Testi nedir ve nasıl olmalı sorularına daha önceki yazılarımda bahsetmiştim ve test kodu yazımında uymamız gereken kurallara değinmiştim. Bu kurallara gerçekleştirim seviyesinde yenilerini ekleyeceğiz.
Bu yazımdaki anlatımda biraz daha domain seviyesinde olaylara bakacağız. Bu yüzden bazı gerçekleştirimleri UML diyagramları üzerinden anlatacağım.
Elimizde bir blog uygulaması olsun. Bu blog uygulamasını ziyaret eden normal kullanıcılar ve blog girişlerini yapan admin kullanıcısı olsun.
[User]-(looksAtListOfBlockEntrySummary)
[User]-(looksAtBlogEntry)
(looksAtListOfBlockEntrySummary)>(visitsHomePage)
(looksAtBlogEntry)>(clicksBlogEntrySummary)
[Admin]-(login)
(login)>(visitsHomePage)
(login)>(fillsLoginForm)
[Admin]-(entersNewBlog)
(entersNewBlog)>(clicksNewEntryTab)
Aşağıdaki diyagramda bir ziyaretçinin yapacağı hareketleri görebilirsiniz.


Admin kullanıcısının 5. gereksiniminde bir iş kuralı (business-rule) vardır ve ilerde kod tekrarına neden olabilir. 5. maddede oluşabilecek kötü kokuları şimdiden almamız gerekir.
Önce Çağır Sonra Gerçekleştir
Burada bir kural daha oluşmakta. Esnek bir test kodu yazmak için belki de öncelikle neyi istediğinizi yazmanız gerekmekte. Bunu yaptıkça gerçekleştirimde nasıl bir yol izleyeceğinizi kurgulayabilirsiniz.
Blog uygulamasında ziyaretçi gereksinimlerini ele alalım. Test etmek istediğim gereksinimler:
1. Gereksinim için
User.visitsHomePage();
assertThat(homePage(), listsBlogEntrySummaries());
2. Gereksinim için
User.visitsHomePage().clicksAnySummary().waitFor(five.seconds());
assertThat(user, visitsBlogDetailPage());
lastClickedBlogSummary = homepage.lastClickedSummary();
assertThat(blogDetailPage(), displaysFullBlogEntry().of(lastClickedBlogSummary ));
3. Gereksinim için
blogEntry = blogEntry().withId(5);
User.visitsBlogDetailPage().forA(blogEntry);
assertThat(blogDetailPage(), displaysFullBlogEntry().of(blogEntry));
İş gereksinimlerini hiçbir yorum yazma gereksinimi duymadan anlaşılır bir şekilde kodlayabildim.
User.visitsHomePage().clicksAnySummary().waitFor(five.seconds());
şeklindeki bir çağrımda visitsHomePage() methodu bir static factory method olup sonraki methodlar User sınıfına ait bir nesneye aittir.
assertThat(blogDetailPage(), displaysFullBlogEntry().of(blogEntry));
şeklindeki çağrımda ise static import yapılarak sadece method isimleri görünmektedir. Bu statik methodları içerikle ilişkili olan bir sınıf içerisinde barındırabilirsiniz. assertThat() methodu junit'in bir methodudur. İkinci parametre olarak bir Hamcrest projesine ait Matcher almaktadır. Siz istediğiniz şekilde kendi mathcer'larınızı oluşturabilirsiniz.
Genel Web Bileşenleri Tanımlayın
Yukarıdaki gereksinim testlerine ait kodlar sürekli değişime uğrayarak daha esnek hale gelecektir. Bu kodlarda beni rahatsız eden bir iki şey var.
Ziyaret Edilebilir Web Sayfası Bileşeni Tanımlayın
Kullanıcı sürekli tekrar eden ve sayfa çeşitlemesine bağlı olarak tekrarlayan visits*() methodunu kullanmakta. Ziyaret edilen sayfa üzerinden daha sonrasında bazı sorgulamalar yapılmakta. Ben burada Page olgusu oluşturmam gerektiğini anlıyorum.
[Page|url]

şeklinde yapı oluşturduktan sonra sayfa ziyaretleri şu hale gelecektir:
User.visits(the.homepage());
User.visits(the.entryDetailPage());
the yardımcı sınıfı içerisinde yardımcı factory methodlar tanımlayabiliriz.
public static homePage () {
return Page.forA("http://www.myblog.com");
}
şeklinde Page nesnelerimizi oluşturabiliriz.
Yukarıda 3. gereksinim için yazdığımı test çağrımında bir sıkıntımız bulunmakta.
User.visitsBlogDetailPage().forA(blogEntry);
Bu çağrım şekli bir blog girişi için detay sayfasının oluşturumunu User sınıfına yüklemektedir. Halbu ki User'in böyle bir sorumluluğu olamaz. Page yapısını oluşturduktan sonra bu çağrım şekli
User.visits(the.entryDetailPage().forA(blogEntry));
olacaktır.
Web Bileşenleri Tanımlayın
Bilgidiğimiz gibi Web uygulaması bir çok temel bileşenin bir araya gelerek oluşturduğu bileşenlerden oluşmaktadır. Mesela kullanıcı giriş formu, blog özeti, blog özet listesi, blog detayı v.b. Yukarıdaki sayfa ziyaretinde olduğu gibi her bir bileşen için bir method tanımlamak anlamsız olacaktır.
Önceki gereksinimlerimizde clicksAnySummary() methodu kötü kokular yaymaktadır. click event'i mouseover, mouseout gibi genel bir methoddur ve bu işlemler bir web bileşeni için çalışmaktadır. Bu methodların PageElement isminde bir yapıdan türemiş bileşenler üzerinde çalışması daha mantıklı gibi. Gereksinimlerimize bakarak aşağıdaki web bileşeni diyagramını oluşturabiliriz.
[PageElement]^[LoginForm]
[PageElement]^[EntrySummary]
[PageElement]^[EntrySummaries]
[PageElement]^[BlogEntryLink]

Web bileşenlerini tanımladıktan sonra çağrımlarımız bu şekilde olacaktır:
User.visits(the.homepage()).clicks(the.homepage().anyBlogSummary());
Web Bileşenlerinin Yüklenmesini Ana Bileşenlerin Sorumluluğuna Verin
Bir sayfadaki bileşenleri Page nesnesi yüklemeli ve bileşen sorgulamasını Page üzerinden yapın the.homepage().hasAnyEntrySummary() çağrımında olduğu gibi.
EntrySummary bileşeni de detay sayfasına yönlendirecek link, özet bilgi ve başlık içermektedir. Bir EntrySummary nesnesi oluşturulurken bu alt bileşenleri yüklemeli. Böylece şu çağrımı yapabilirim:
String entrySummaryText = the.homepage().selectAnyEntrySummary().content();
Bu yazımdaki anlatımda biraz daha domain seviyesinde olaylara bakacağız. Bu yüzden bazı gerçekleştirimleri UML diyagramları üzerinden anlatacağım.
Elimizde bir blog uygulaması olsun. Bu blog uygulamasını ziyaret eden normal kullanıcılar ve blog girişlerini yapan admin kullanıcısı olsun.
[User]-(looksAtListOfBlockEntrySummary)
[User]-(looksAtBlogEntry)
(looksAtListOfBlockEntrySummary)>(visitsHomePage)
(looksAtBlogEntry)>(clicksBlogEntrySummary)
[Admin]-(login)
(login)>(visitsHomePage)
(login)>(fillsLoginForm)
[Admin]-(entersNewBlog)
(entersNewBlog)>(clicksNewEntryTab)
Aşağıdaki diyagramda bir ziyaretçinin yapacağı hareketleri görebilirsiniz.

- Bir blog sayfasını açar
- Anasayfada tanımlı blog girişlerinin özetleri listelenmiştir
- Bu özetlerden birine tıkladığında tam halini görmektedir

- Blog sitesini açar
- Login formunu doldurur
- Yeni Giriş tabına tıklar
- Blog girişini yapar
- Ayrıca ziyaretçinin yaptığı tüm hareketleri yapar
Admin kullanıcısının 5. gereksiniminde bir iş kuralı (business-rule) vardır ve ilerde kod tekrarına neden olabilir. 5. maddede oluşabilecek kötü kokuları şimdiden almamız gerekir.
Önce Çağır Sonra Gerçekleştir
Burada bir kural daha oluşmakta. Esnek bir test kodu yazmak için belki de öncelikle neyi istediğinizi yazmanız gerekmekte. Bunu yaptıkça gerçekleştirimde nasıl bir yol izleyeceğinizi kurgulayabilirsiniz.
Blog uygulamasında ziyaretçi gereksinimlerini ele alalım. Test etmek istediğim gereksinimler:
- Anasayfa blog girişlerinin özetlerini listeler
- Blog özetine tıklandığında o girişin detayı görüntülenir
- Blog Detay Sayfası blog girişinin tamamını görüntüler
1. Gereksinim için
User.visitsHomePage();
assertThat(homePage(), listsBlogEntrySummaries());
2. Gereksinim için
User.visitsHomePage().clicksAnySummary().waitFor(five.seconds());
assertThat(user, visitsBlogDetailPage());
lastClickedBlogSummary = homepage.lastClickedSummary();
assertThat(blogDetailPage(), displaysFullBlogEntry().of(lastClickedBlogSummary ));
3. Gereksinim için
blogEntry = blogEntry().withId(5);
User.visitsBlogDetailPage().forA(blogEntry);
assertThat(blogDetailPage(), displaysFullBlogEntry().of(blogEntry));
İş gereksinimlerini hiçbir yorum yazma gereksinimi duymadan anlaşılır bir şekilde kodlayabildim.
User.visitsHomePage().clicksAnySummary().waitFor(five.seconds());
şeklindeki bir çağrımda visitsHomePage() methodu bir static factory method olup sonraki methodlar User sınıfına ait bir nesneye aittir.
assertThat(blogDetailPage(), displaysFullBlogEntry().of(blogEntry));
şeklindeki çağrımda ise static import yapılarak sadece method isimleri görünmektedir. Bu statik methodları içerikle ilişkili olan bir sınıf içerisinde barındırabilirsiniz. assertThat() methodu junit'in bir methodudur. İkinci parametre olarak bir Hamcrest projesine ait Matcher almaktadır. Siz istediğiniz şekilde kendi mathcer'larınızı oluşturabilirsiniz.
Genel Web Bileşenleri Tanımlayın
Yukarıdaki gereksinim testlerine ait kodlar sürekli değişime uğrayarak daha esnek hale gelecektir. Bu kodlarda beni rahatsız eden bir iki şey var.
Ziyaret Edilebilir Web Sayfası Bileşeni Tanımlayın
Kullanıcı sürekli tekrar eden ve sayfa çeşitlemesine bağlı olarak tekrarlayan visits*() methodunu kullanmakta. Ziyaret edilen sayfa üzerinden daha sonrasında bazı sorgulamalar yapılmakta. Ben burada Page olgusu oluşturmam gerektiğini anlıyorum.
[Page|url]

şeklinde yapı oluşturduktan sonra sayfa ziyaretleri şu hale gelecektir:
User.visits(the.homepage());
User.visits(the.entryDetailPage());
the yardımcı sınıfı içerisinde yardımcı factory methodlar tanımlayabiliriz.
public static homePage () {
return Page.forA("http://www.myblog.com");
}
şeklinde Page nesnelerimizi oluşturabiliriz.
Yukarıda 3. gereksinim için yazdığımı test çağrımında bir sıkıntımız bulunmakta.
User.visitsBlogDetailPage().forA(blogEntry);
Bu çağrım şekli bir blog girişi için detay sayfasının oluşturumunu User sınıfına yüklemektedir. Halbu ki User'in böyle bir sorumluluğu olamaz. Page yapısını oluşturduktan sonra bu çağrım şekli
User.visits(the.entryDetailPage().forA(blogEntry));
olacaktır.
Web Bileşenleri Tanımlayın
Bilgidiğimiz gibi Web uygulaması bir çok temel bileşenin bir araya gelerek oluşturduğu bileşenlerden oluşmaktadır. Mesela kullanıcı giriş formu, blog özeti, blog özet listesi, blog detayı v.b. Yukarıdaki sayfa ziyaretinde olduğu gibi her bir bileşen için bir method tanımlamak anlamsız olacaktır.
Önceki gereksinimlerimizde clicksAnySummary() methodu kötü kokular yaymaktadır. click event'i mouseover, mouseout gibi genel bir methoddur ve bu işlemler bir web bileşeni için çalışmaktadır. Bu methodların PageElement isminde bir yapıdan türemiş bileşenler üzerinde çalışması daha mantıklı gibi. Gereksinimlerimize bakarak aşağıdaki web bileşeni diyagramını oluşturabiliriz.
[PageElement]^[LoginForm]
[PageElement]^[EntrySummary]
[PageElement]^[EntrySummaries]
[PageElement]^[BlogEntryLink]

Web bileşenlerini tanımladıktan sonra çağrımlarımız bu şekilde olacaktır:
User.visits(the.homepage()).clicks(the.homepage().anyBlogSummary());
Web Bileşenlerinin Yüklenmesini Ana Bileşenlerin Sorumluluğuna Verin
Bir sayfadaki bileşenleri Page nesnesi yüklemeli ve bileşen sorgulamasını Page üzerinden yapın the.homepage().hasAnyEntrySummary() çağrımında olduğu gibi.
EntrySummary bileşeni de detay sayfasına yönlendirecek link, özet bilgi ve başlık içermektedir. Bir EntrySummary nesnesi oluşturulurken bu alt bileşenleri yüklemeli. Böylece şu çağrımı yapabilirim:
String entrySummaryText = the.homepage().selectAnyEntrySummary().content();
Kaydol:
Kayıtlar (Atom)