Thursday, March 22, 2007

แด่แม่พลอย(สี่แผ่นดิน)

พลอยเป็นคนเชยมากนะครับ เป็นคนที่อยู่ในกรอบ ใจดี ถูกจับคลุมถุงชนแต่งงานก็หลงรักคุณเปรมได้ ที่นี้คนอ่านคนไทยปลื้มอกปลื้มใจเห็นแม่พลอยเป็นคนประเสริญเลิศลอย ก็เพราะคนไทยก็เป็นคนแบบนั้น ยังไม่ได้ไปถึงไหนเลย คนอ่านส่วนมากก็เป็นคนระดับแม่พลอยเท่านั้น(หัวเราะ) โง่ฉิบหาย

บทสัมภาษณ์ "คึกฤทธิ์คิดลึก ทศกัณฐ์วรรณกรรม" ใน ถนนหนังสือ 3, 1 กรกฏาคม 2528

Related link from Roti

Tuesday, March 20, 2007

ruby กับ gcd

คุณ sugree พูดถึง gcd ใน howforge
และไปตั้งโจทย์ไว้ใน codenone.com

ผมก็เลยลองไปแคะดูว่า method หรือ function gcd ใน ruby นั้นมีอยู่หรือยัง
ลองอ่านดูใน document ของ ruby
ก็พบว่า class Integer มี method ที่ทำ gcd อยู่ 2 version ก็คือ
gcd กับ gcd2
ในเอกสาร ก็ไม่ได้อธิบายว่าทำไมถึงมี 2 version
แต่เดาได้เลยว่า น่าจะเกี่ยวกับ algorithm ในการ implement

เพื่อแก้ความสงสัย ผมก็เลยทดสอบจัด benchmark เปรียบเทียบดู
require 'benchmark'
r = (1..10000)
m = 70
Benchmark.bm do |x|
x.report("gcd") { r.each {|n| n.gcd m}}
x.report("gcd2") { r.each {|n| n.gcd2 m}}
end

ผลที่ได้ ก็คือ gcd2 นั้นทำงานเร็วกว่า
      user     system      total        real
gcd 0.300000 0.000000 0.300000 ( 0.303847)
gcd2 0.130000 0.000000 0.130000 ( 0.126843)


ถ้าลองแกะดู source code ของ ruby 1.8.4 จะเห็นว่า gcd2
ใน algorithm แบบ Euclidean algorithm
มีหน้าตาแบบนี้
def gcd2(int)
a = self.abs
b = int.abs

a, b = b, a if a < b

while b != 0
void, a = a.divmod(b)
a, b = b, a
end
return a
end


ที่สนุกกว่านั้นก็คือ ใน library 'mathn' มันมีการ override
method gcd2 ด้วย algorithm "prime factorizations"
ผลที่ได้คือ ช้าสุดๆ
ลองดู benchmark ของ gcd2 ที่ถูก override แล้ว
require 'mathn'
Benchmark.bm do |x|
x.report("gcd2-mathn") { x.each {|n| n.gcd2 m}}
end

      user     system      total        real
gcd2-mathn 4.450000 0.030000 4.480000 ( 4.545717)


จะเห็นว่า การ re-open-class ได้แบบ ruby มันมาทั้งข้อดีและข้อเสีย
อย่างตัวอย่างข้างบน programmer คนแรกรู้ว่า gcd2 มันเร็ว
ก็เลยเขียน code โดยเรียกใช้ gcd2
แต่อยู่มาวันหนึ่งมีคนเกิดอยากได้ function ที่อยู่ใน 'mathn' เข้า
effect ที่เกิดจากการ require 'mathn'
ก็จะทำให้ gcd2 ที่เคยเร็วจี๋เปลี่ยนเป็นช้าสุดๆ โดยไม่มีใครรู้ตัว

จากการไล่ดู code ต่อ ก็พบว่า ruby 1.8.6 มีการเปลี่ยนแปลงใหม่
โดยตัด method gcd2 ออกไป และเปลี่ยนให้ gcd ไปใช้ Euclidean algorithm แทน

Note: ผมเขียนโปรแกรมมาก็นานแล้ว ยังไม่เคยใช้ gcd เลย

Related link from Roti

Friday, March 16, 2007

เกินมาจากไหน

เมื่อ 3 วันก่อน Database ของระบบที่ให้บริการอยู่เกิดล่มลง
จากการวิเคราะห์ log files ต่างๆ
มีข้อสันนิษฐานว่า จำนวน sessions ที่ client connect เข้ากับ database
มันเกิดค่าที่ตั้งไว้ (ไม่ใช่สาเหตุตรงๆที่ทำให้ล่ม แต่มันทำให้ resources ต่างๆมันไม่พอ)

จากการนับจำนวน user ที่เป็น client ต่อตรง ณ ขณะนั้น
มีจำนวน 262 จอ
นำจำนวน session มาลบออก ก็จะได้เป็นจำนวน connection
ที่ web application ต่อ = 485 - 262 = 218 connection

มาจากไหนวะ ตั้ง 218, connection leak เหรอ
จากการคำนวณแล้ว connection ทั้งหมดที่ web app พึงใช้น่าจะประมาณ 52 connection

ปัญหานี้เคยคิดไว้เหมือนกัน
เลยทำหน้าจอสอบถามใน web app ให้ admin สามารถดูได้ว่า
connection pool size ที่ใช้อยู่ปัจจุบันมีค่าเท่าไร
เปิดเข้าไปดูใน app
ปรากฎว่ามี active อยู่แค่ 5
แล้วมันจะกลายเป็น 218 ได้ไงวะเนี่ย

ผ่านไป 3 วัน
เข้าไปอ่านทบทวน file ~tomcat/conf/context.xml ที่ config Datasource ไว้
<!-- The contents of this file will be loaded for each web application -->
<Context>

<Resource auth="Container"
description="DB
Connection"
driverClass="ca.ingres.jdbc.IngresDriver"
maxPoolSize="21"
minPoolSize="5"
acquireIncrement="5"
name="jdbc/ssoDS"

...
</context>


อ่านบรรทัดแรกปุ๊บ สะดุ้งโหย๋ง
ชิบหาย web application เรามีตั้ง 10 กว่า app
มิน่าเล่า
ค้นหาเอกสาร tomcat ดู
ถ้าอยาก config datasource ให้ share กันระหว่าง context ได้
ต้อง config ไว้ใต้ elememt GlobalNamingResources ใน file server.xml

จากการสอบถาม เจ้าตัวผู้เป็นคน config
เขาบอกว่า เดิมก็พยายาม config ไว้ใน GlobalNamingResources เหมือนกัน
แต่ทำแล้วไม่ work สักที ก็เลยย้ายไปลองใส่ไว้ใน context.xml
ซึ่งใส่ปุ๊บก็ work เลย.

เข้าข่าย programming by coincident อีกแล้ว

Related link from Roti

Thursday, March 15, 2007

Terracotta #2

ต่อจากตอนที่แล้ว

Roots
คือ objects ที่เราต้องการให้ทุก node ที่เข้ามา join cluster
มองเห็นเป็น object เดียวกันหมด

สิ่งที่เกิดขึ้นเมื่อเรา define root object ก็คือ
ตอนที่ class loader กำลังเริ่ม load class
จะมีการ add Advice (AOP) แบบนี้เข้าไป
Object around() : get(@Root * *.*) {
String name = thisJoinPointStaticPart.getSignature().toLongString();
return ClusterManager.getOrCreateRoot(name);
}

ซึ่งพฤติกรรมของมันก็คือ เมื่อมีการพยายามที่จะ get ค่า จาก field ที่เป็น root
around advice ก็จะ intercept การ access นั้น
แล้ว delegate ต่อไปยัง ClusterManager แทน
ซึ่ง ClusterManager ก็จะทำการ connect ไปยัง Terracotta Server
เพื่อขอค่า instance ของ root object นั้นมา

ที่ฝั่ง server เมื่อได้รับ request ก็จะทำการตรวจสอบว่า
ใน repository ของตัวเองมี root object ตามชื่อนั้นหรือไม่
ถ้าไม่มี ก็จะทำการสร้างขึ้นมาใหม่ให้ แต่ถ้ามีก็จะทำการส่ง Object นั้นกลับมา

จะเห็นได้ว่า Terracotta Server จะเป็น Node หลักที่มี Data เก็บไว้ในเครื่องตัวเอง
ส่วน Virtual machine อื่นๆ ที่ join เข้าไป ก็จะมี replicate set ของ Data นั้นๆ

object ที่จะกำหนดให้เป็น Root จะเป็น Object ใดๆ ก็ได้
ไม่จำกัดว่าต้องเป็น Serializable
กลไกที่ Terracotta ใช้ replicate changed จะมีลักษณะเป็น fine grained
นั่นคือ replicate เฉพาะส่วนที่เปลี่ยนแปลง ไม่ได้ทำหมดทั้งก้อน

จากตัวอย่างเมื่อวาน ถ้าเราลองเปิด admin UI ของ Terracotta ดู
จะเห็นว่าเรามี root object ที่ชื่อ bid.Bidding.bid



Virtual Heap
เมื่อมีการ define Root Object ก็ต้องมีการ replicate object นั้น ไปให้ทุก Virtual machine ที่ต้องการใช้
วิธีการที่ Terracota ใช้ก็คือการ มีการกำหนดพื้นที่สำหรับเก็บข้อมูลบนแต่ละ Virtaul machine ขึ้นมา
ซึ่งเรียกว่า Virtual Heap
การเก็บข้อมูลใน Virtual Heap จะใช้หลักเดียวกับ cache ก็ืคือ
พื้นที่เก็บมีจำกัด ข้อมูลไหนที่ไม่ค่อยได้ใช้ จะต้องถูก flush ออก เพื่อเปิดทางให้กับข้อมูลที่ถูกใช้บ่อยๆแทน

เวลาที่ Node ของเรา เริ่ม join เข้า cluster
Terracota จะยังไม่ copy ข้อมูลจาก server มาทันที
แต่จะรอให้มีการ access object นั้นก่อน จึงจะ copy ข้อมูลมาใส่ไว้ใน Virtual Heap ให้
(ทำในลักษณะ fine grained นั่นคือ เฉพาะ field ที่เกิดการ access จริงๆ)

Related link from Roti

Wednesday, March 14, 2007

Terracotta #1

ได้ฤกษ์ทดลอง Terracotta แล้ว หลังจากที่ดองเรื่องไว้นานแสนนาน
สำหรับคนที่ไม่รู้จัก Terracotta
Terracotta คือ clustering Techonology อันหนึ่ง
ที่ใช้หลักการ AOP เข้าไปเปลี่ยนแปลง java byte code ของเรา
ทำให้ POJO Object ของเราประพฤติตนในลักษณะ cluster ได้

AOP library ที่ Terracotta เลือกใช้ก็คือ AspectWerkz
ส่วน byte code manipulation library เขาใช้ ASM

สิบปากว่าไม่เท่าตาเห็น
ทดสอบเขียน application ง่ายๆ ในเรื่องการประมูล

เริ่มด้วย class Bid แทน item ที่กำลังถูกประมูลอยู่
ภายในมี information แค่ว่า ใครประมูลล่าสุด ด้วยราคาเท่าไร
package bid;

import java.math.BigDecimal;

public class Bid {
private String name = "";
private BigDecimal currentPrice = BigDecimal.ZERO;

public BigDecimal getCurrentPrice() {
return currentPrice;
}
public void setCurrentPrice(BigDecimal currentPrice) {
this.currentPrice = currentPrice;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}


จากนั้นก็ทดลองเขียน class Bidding ที่เป็น console application ที่ใช้ในการประมูล
โดยวิธีใช้งานมันจะหน้าตาประมาณนี้
Command (b-bid, n-new, s-show, q-quit) : n
id ? 1
Command (b-bid, n-new, s-show, q-quit) : s
id : 1, -> name = , currentPrice = 0
Command (b-bid, n-new, s-show, q-quit) : b
id ? 1
name :pok
price :200
Command (b-bid, n-new, s-show, q-quit) : s
id : 1, -> name = pok, currentPrice = 200
Command (b-bid, n-new, s-show, q-quit) : q

ตั้งโครง class Bidding ก่อน โดยในการประมูลครั้งหนึ่งๆ เรามี item ที่จะประมูลได้หลายอันพร้อมๆกัน
ดังนั้นเราจึงใช้ HashMap ในการเก็บ instance Bid ที่กำลังประมูลอยู่
public class Bidding { 

ConsoleReader console;
HashMap<String,Bid> bids = new HashMap<String,Bid>();

public static void main(String[] args) throws IOException {
new Bidding().run();
}
}

method run ก็คือ method ที่ใช้ loop เพื่อรับ input จากหน้าจอ console ของ user
    public void run() throws IOException {
console = new ConsoleReader();

while (true) {
String tmp = console.readLine("Command (b-bid, n-new, s-show, q-quit) : ");

switch (tmp.charAt(0)) {
case ('q'):
return;
case ('n'):
newBid();
break;
case ('s'):
show();
break;
case ('b'):
bid();
break;
}
}
}

method newBid คือ method ที่ใช้สร้าง bid item อันใหม่
    public void newBid() throws IOException {
String id = console.readLine("id ? ");
Bid bid = new Bid();
bids.put(id, bid);
}

method show ทำหน้าที่ print bid item ทั้งหมด
    public void show() throws IOException {
for (String key : bids.keySet()) {
Bid bid = bids.get(key);
console.printString("id : " + key + ", -> ");
console.printString("name = " + bid.getName() + ", currentPrice = " + bid.getCurrentPrice());
console.printNewline();
}
}

method bid ก็คือการเสนอราคาประมูล
    public void bid() throws IOException {
String id = console.readLine("id ? ");
Bid bid = bids.get(id);
String bidder = console.readLine("name :");
String price = console.readLine("price :");
BigDecimal p = new BigDecimal(price);
if (p.compareTo(bid.getCurrentPrice()) > 0) {
bid.setCurrentPrice(p);
bid.setName(bidder);
}
}


จะเห็นว่าเราเขียนโดยไม่มีแนวคิดเรื่อง cluster หรือ share thread อยู่ในนี้เลย
ทีนี้ก็มาถึงคำถามว่า ถ้าเราต้องการจะให้มันเป็น cluster ขึ้นมาหล่ะ
เราจะทำอย่างไร
เริ่มด้วยลักษณะ cluster ที่เราต้องการก่อน
เราจะ start program bidding หลายๆตัว บน virtual machine คนละตัวกัน
แต่เราต้องการให้ bidding มองเห็น instance variable bids (HashMap) ของทุกตัวเป็นตัวเดียวกัน
จะได้แย่งกันประมูลได้

เริ่มแรกสุด terracotta มี architecture เป็นแบบ hub and spoke
ไม่ได้เป็น peer-to-peer ดังนั้นเราต้อง start terracotta server ขึ้นมาก่อน
โดยใช้คำสั่ง start-tc-server.sh

จากนั้นเราก็ต้องกำหนด configuration เพื่อให้ terracotta รู้ว่า
ควรจะตัดต่อ code เราตรงไหนบ้าง
โดยสิ่งแรกที่ต้องกำหนดก็คือ root object
root object คือ object ที่เราอยากให้ ทุกๆ virtual machine ที่ join เข้าวง cluster มองเห็นเป็นตัวเดียวกัน
ในกรณีของเรา root object ก็คือ

HashMap<String,Bid> bids = new HashMap<String,Bid>();

ซึ่งใน config ของ terracotta เราจะกำหนดโดยใช้ xml ดังนี้
<roots>
<root>
<field-name>bid.Bidding.bids</field-name>
</root>
</roots>


เมื่อกำหนด root object แล้ว
เราก็ต้องบอกให้ terracotta จัดการ modify access method ทุกๆอันของเรา
ที่มีการเรียกใช้ root object
โดยเราสามารถเลือก lock ว่าจะเป็น read lock, write lock, และ concurrent

เริ่มด้วย method show ก่อน
อันนี้เลือกได้ว่าจะเป็น read หรือ concurrent หรือไม่กำหนดเลยก็ได้
ถ้าเป็น read ถ้ามันเจอใครกำลัง write อยู่ มันจะ wait รอ
ส่วน concurrent เป็นแบบที่ไม่สนว่าใครกำลังทำอะไร จะเขียนหรืออ่านก็ได้
ลองกำหนดเป็นแบบ read จะได้ไม่เจอ case แปลกๆเช่น ชื่อเป็นชื่อของคนคนหนึ่งและราคาเป็นของอีกคนๆหนึ่ง
  <named-lock>
<lock-name>show</lock-name>
<method-expression>* bid.Bidding.show(..)</method-expression>
<lock-level>read</lock-level>
</named-lock>

สังเกตุว่ามันมี type เป็น named-lock
ซึ่งมีความหมายว่า method ที่เราร้อย AOP นี้ไม่ได้เขียนแบบคำนึงถึง multi-thread มาก่อน (ไม่ได้ใช้ keyword พวก synchronized)
โดยเราต้องกำหนด name ของ lock ให้มันด้วย

จากนั้นก็ว่าด้วย method newBid โดย
เรากำหนดให้เป็น write lock
  <named-lock>
<lock-name>newBid</lock-name>
<method-expression>* bid.Bidding.newBid(..)</method-expression>
<lock-level>write</lock-level>
</named-lock>


method สุดท้ายก็คือ bid
method นี้พิเศษหน่อยตรงมันจะมี process การอ่าน console ก่อน จากนั้นจึงค่อย เขียนลงไป
เราเลยลองใช้ synchronized ครอบลงไป เพื่อไม่ให้เจอ user ใส่ข้อมูลค้างไว้
แล้วลุกไปกินกาแฟ ทำให้ระบบเราหยุดเพราะเจอ write lock เปิดทิ้งไว้
แก้ code ดังนี้
        synchronized(bids) {
if (p.compareTo(bid.getCurrentPrice()) > 0) {
bid.setCurrentPrice(p);
bid.setName(bidder);
}
}

จากนั้นก็กำหนด configuration ของ lock
แต่คราวนี้จะสังเกตว่าเราเปลี่ยนไปใช้ autolock type แทน
  <autolock>
<method-expression>* bid.Bidding.bid(..)</method-expression>
<lock-level>write</lock-level>
</autolock>


เสร็จ แค่นี้เราก็ทดลอง run มันจาก หลายๆ virtual machine เข้าไปประมูล
บน bids ชุดเดียวกันได้แล้ว
เวลา run ก็ใช้คำสั่งประมาณนี้
โดย tc-config.xml ก็คือ configuration file ของเรา
~/dev/terracotta-2.2/dso/bin/dso-java.sh -cp bin:jline-0.9.91.jar -Dtc.config=tc-config.xml bid.Bidding 


ง่ายจนน่าตกใจ

Related link from Roti

Tuesday, March 13, 2007

Propaganda

รอบกายเรานี้มีแต่ propaganda
ดังนั้นเราควรจะเข้าใจกฎเกณฑ์พื้นฐานของมันหน่อย

Theorists of propaganda have identified five basic rules:

1. The rule of simplification: reducing all data to a simple confrontation between “Good and Bad”, “Friend and Foe”.
ผมเคยถกประเด็น technique กับ it consultant ที่มีอาชีพทนายมาก่อน
เจอ technique นี้เข้าไป แพ้หลุดลุ่ยเลย
ไอ้เราก็มัวอักๆอักๆ เพราะสำหรับเราแล้ว
ทุกๆอย่างมันมีทั้งข้อดีและข้อเสีย

2. The rule of disfiguration: discrediting the opposition by crude smears and parodies.
ป้ายสีอีกฝ่ายเสีย

3. The rule of transfusion: manipulating the consensus values of the target audience for one’s own ends.
เกาะกระแส
หาประโยชน์จากเสียงส่วนใหญ่

4. The rule of unanimity: presenting one’s viewpoint as if it were the unanimous opinion of all right-thinking people: drawing the doubting individual into agreement by the appeal of star-performers, by social pressure, and by “psychological contagion”.
(unanimity แปลว่า การยอมรับอย่างเป็นเอกฉันท์)
โชคร้ายที่เราไม่มีกลไกที่จะรู้ได้ว่า เสียงส่วนใหญ่เป็นอย่างไร
นักการเมือง ก็มักอ้างว่าความคิดเห็นตัวเองตรงเสียงส่วนใหญ่
ส่วนใครที่ค้าน ก็มักจะถูกจัดการว่า คุณต้องยอมรับประโยชน์ส่วนรวม, หรือใช้ชั้นเชิงการแสดงชักจูง

5. The rule of orchestration: endlessly repeating the same messages in different variations and combinations.
ตัวอย่างที่เห็นชัด ก็คือ โครงการที่เกี่ยวกับสถาบันกษัตริย์ (ไม่ได้พูดประเด็นถูกผิดนะ)

The Five rules of propaganda

Related link from Roti

Monday, March 12, 2007

ว่ายน้ำในทะเล

อาทิตย์ที่ผ่านมา ผมพาแฟนและลูกชายไปเที่ยวหาดเจ้าหลาว, จันทบุรีมา
ไม่นึกว่าชายหาดเมืองจันฯ ก็สวยไม่แพ้ที่อื่นเหมือนกัน
แต่ที่ชอบก็คือน้ำทะเลช่วงนี้ ที่เรียบสนิท ปราศจากคลื่น

กิจกรรมหนึ่งที่ผมขาดไม่ได้เวลาไปเที่ยวทะเล
ก็คือการว่ายน้ำในทะเล ที่ภาษาอังกฤษเขาใช้คำว่า open water swimming
ประเด็นที่ผมหลงไหลในการว่ายน้ำทะเล มีอยู่ดังนี้
1. ผมชอบบรรยากาศ สีสรร ของใต้น้ำ สีกับแสงมันสวยดี (จริงๆแล้วมันมีแค่ฟ้ากับเขียว แต่มันมีหลายเฉดสี)
สัมผัสของน้ำก็ทำให้รู้สึกดี
2. ความหนาแน่นของน้ำทะเล ทำให้เรารู้สึกตัวเบา ว่ายเหนื่อยน้อยกว่าในน้ำจึดเยอะ
3. ความรู้สึกเปิด อิสระ, ไม่มีลู่ว่าย ไม่มีเส้นสีดำบนพื้นสระที่กำหนดแนวว่าย
ไม่มีผนังสระที่ต้องกลับตัว, ไม่มี boundery ที่บอกว่า เฮ้ยว่ายครบ 100 metre แล้ว
มีแต่ว่ายเท่าไรก็ไปไม่ถึงไหน
4. ภาวะสมาธิที่เกิดจากความเงียบ,การทำซ้ำ และการ focus ที่การเคลื่อนไหว



อุปสรรคของการว่ายน้ำในทะเล
เท่าที่เจอมาก็มี
1. ความกลัว อันนี้เป็นผลมาจากหนังที่เกี่ยวฉลามทั้งหลาย



พอเข้าเขตที่ลึก วิตกจริตจะมาก่อนเลย
ยิ่งที่ลึกๆและน้ำใสๆ ถ้าความลึกเกิน 80 ฟุต สีของน้ำจะเป็นสีน้ำเงินเข้มเลย
ที่เคยกลัวที่สุดก็คือ ครั้งที่ไปดำน้ำแถวตรัง
โดยเรือมุ่งหน้าออกจากภูเก็ต และระหว่างทางมีการจอดเรือแวะพักทานข้าวที่ข้างเกาะแห่งหนึ่ง
ผมก็เลยได้ทีกระโดดน้ำว่ายเข้าเกาะ
แต่เนื่องจากเกาะมีลักษณะเป็นภูเขาใต้น้ำที่โผล่ยอดขึ้นมา ไม่มีหาดทราย ไม่มีทางลาดใต้น้ำ ที่ใล่จากตื้นไปลึก
ว่ายไปได้สักพัก จินตนาการก็เริ่มทำงาน
ปลาฉลามตัวใหญ่มันจะอยู่แถวนี้หรือเปล่าวะ
พยายามสงบจิตสงบใจว่ายต่อจนถึงเกาะ (ซึ่งก็น่ากลัวไปอีกแบบ เพราะเป็นหน้าผาทั้งบนบกและใต้น้ำ)
แต่อาการก็ไม่หาย ขาว่ายกลับเรือนี่เป็นเอามาก
สุดท้ายก็เลยต้องเปลี่ยนไปว่ายท่ากบ
เพื่อลดเวลาที่ต้องมองจ้องใต้น้ำ ให้น้อยที่สุด
(ไม่มองก็ไม่ได้ เพราะจินตนาการเรื่องฉลาม มันบังคับให้เราต้องเฝ้าระวังดูใต้น้ำ)

ความกลัวฉลามนี้จะไม่เกิดถ้ามีเพื่อนว่ายไปด้วย
อย่างที่เคยว่ายที่เกาะโลซิน ปัตตานี ซึ่งน้ำลึก > 80 ฟุต
ครั้งนั้นมีเพื่อนว่ายไปด้วย ว่ายอยู่ชั่วโมงกว่า ไม่รู้สึกกลัวเลย

2. แมงกระพรุน และแตนทะเล
ชื่อหลังนี้ไม่รู้ชื่อจริงๆของมัน รู้แต่ว่า มันมองไม่เห็น แต่โดนแล้วทั้งเจ็บและแสบเป็นบ้าเลย
แถมยังมีเยอะด้วย ว่ายทีไรก็โดนทุกที
ยิ่งถ้าโดนที่ปาก ก็จะเจ็บเป็นพิเศษเลย
ทะเลแถวอันดามัน นี่เป็นภาคบังคับเลย ไม่เจอก็แปลก
ส่วนแมงกระพรุนนั้นโชคดีว่ายังไม่เคยเจอแมงกระพรุนไฟ
เคยเจอแต่แมงกระพรุนธรรมดา
ซึ่งเวลาเจอก็มักจะเกิดอาการสะดุ้งตกใจ
เพราะมือหรือขามักไปโดนก่อนที่ตาจะเห็น

ที่ประทับใจที่สุด ก็เป็นที่เกาะช้าง
เพราะเคยเจอแมงกระพรุนตัวเล็กๆขนาดประมาณ 1 นิ้ว
ตัวใสมากถ้าอยุ่ในน้ำทะเลนี่จะมองไม่เห็น
เคยว่ายแหวกเข้าไปในฝูงใหญ่ของมัน
ได้อารมณ์ชวนสะดุ้งมากเลย มองไม่เห็นแต่สัมผัสได้

3. กระแสน้ำ
อันนี้มักจะเจอตามเกาะ, พวกชายฝั่งธรรมดายังไม่เคยเจอ
ที่แรงสุดที่เคยเจอก็ที่เกาะแสมสาร สัตหีบ
ผมลงจากหาดทรายบนเกาะไปว่ายน้ำเล่น
ว่ายออกไปไม่กี่เมตร ก็เจอกระแสน้ำแล้ว
พัดทีตัวปลิวเลย
เวลาจะกระแสน้ำแรงๆ ท่าว่ายที่เหมาะที่สุดก็คือท่ากบ

4. อึ
ไม่ได้สะกดผิด, อันนี้เกิดขึ้นเวลาที่เราว่ายจากฝั่งไปขึ้นเรือ
ซึ่งบันใดเรือมันมักจะอยู่ท้ายเรือข้างๆห้องน้ำ
อันนี้ต้องเล็งและกะจังหวะให้ดี
ไม่งั้นจะเกิดภาวะสยองของการว่ายฝ่าเหล่าของเสียสีเหลืองได้

Related link from Roti

Wednesday, March 07, 2007

Erlang Book

3-4 วันที่ผ่านมานี้, mailing list ของ ruby กับ erlang
มีการ post announce ว่า Pragmatic Bookshelf จะออกหนังสือเล่มใหม่
เรื่อง Programming Erlang. สร้างความตื่นเต้น (วัดจาก feedback) ให้กับ mailing list ทั้งสองได้ไม่น้อย

สิ่งที่น่าสนใจก็คือ
ในกรณีนี้หัวข้อหนังสือคือ erlang
ดังนั้นไม่แปลกที่ mailing-list ของ erlang ก็ควรจะคึกครื้นเป็นธรรมดา
แล้ว่ทำไม่ mailing-list ของ ruby ถึงคึกไปด้วย
เหตุผลก็น่าจะประมาณนี้
1. Pragmatic Bookshelf หนังสือส่วนใหญ่ focus พวก ruby, rails, agile
มีกลุ่มลูกค้าชัดเจน มี brand royalty ค่อนข้างสูง
แฟนๆ ruby ก็เลยชอบไปหมด ไม่ว่าจะออกหนังสืออะไรมา
2. คนชอบ Ruby เป็นพวก "hyper-enthusiasts"
มีอะไรใหม่ๆ มันส์ๆ ก็ชอบ
สังเกตจากใน mailing list ก็มีหลายคนเขียนตอบไปว่า
มี erlang แล้ว อย่าลืมออกหนังสือ haskell ด้วยหล่ะ
ซึ่ง Pragmatic ก็ตอบไปว่า "อยากออก แต่ขั้นแรก ต้องหาคนเขียนที่เหมาะสมให้ได้ก่อน"

Related link from Roti

Tuesday, March 06, 2007

module_function [Ruby]

ziddik ถามเรื่องพฤติกรรมแปลกๆของ module_function
สำหรับคนที่ไม่รู้จัก module_function ลองมาดูว่ามันคืออะไรก่อน

สมมติเรา define Library math ไว้แบบนี้
module Math
def sin(x)
...
end
end


ปัญหาก็คือ คนใช้ต้อง include Math ก่อน
จึงจะใช้ได้
include Math
puts sin(10)

ซึ่งดูไม่สะดวกนัก
เช่นอาจจะเกิด name conflict ได้ง่าย

module function เข้ามาช่วยในจุดนี้โดย
module Math
def sin(x)
...
end

module_function :sin
end

จะทำให้เราสามารถเรียกใช้แบบนี้ได้
Math.sin(10)
หรือ
include Math
sin(10)


ขั้นตอนภายในจะแปลกๆหน่อยตรง
1. มันจะ copy function sin เป็น instance method อันใหม่
มี code แยกออกไปจากกัน (ไม่ share กัน)
2. มันจะ mark method ให้กลายเป็น private

ส่วนคำถามว่าทำไม มันถึง mark ให้เป็น private
มีคนพยายามเดาใจ Matz เหมือนกัน
แต่ผมอ่านแล้วก็ยังไม่เข้าใจ
Re: module_functions are private?

ส่วนตัวอย่างในคำถามของ ziddik
ถ้าอยากให้ใช้ได้ ต้องเขียนเป็นแบบนี้
module HelloModule
Version = "1.0"

def hello(name)
puts("Hello, #{name}. ")
end

module_function :hello
end


class Hello
include HelloModule
def myHello(name)
hello(name)
end
end

หรือจะ ใส่ public :hello ใน class Hello ก็ได้
แต่ตามที่ใน mailing list บอก
การทำเช่นนี้อาจจะเปิดโอกาสให้
shooting yourself in the foot.

Note: ผม search code ใน rails ดู
พบว่ามีการใช้ module_function อยู่นิดหน่อย
ใน package actionMailer กับ actionWebService
(ใช้อยู่นิดเดียว)

Related link from Roti

Ruby 's Require ภาค 2

ปกติถ้าเราจะใช้ class อะไร
เราก็ต้อง load class นั้นขึ้นมาใน context ของเราก่อน
เช่นเรามี file p.rb
class Pok
def hi
"hello"
end
end

ลอง สั่ง irb
$ irb
>> Pok.new
NameError: uninitialized constant Pok
from (irb):1
>> require 'pok'
=> true
>> Pok.new
=> #<Pok:0x11158a8>

นี่คือกลไกปกติใน ruby
แต่ถ้าใครเคยใช้ rails แล้ว จะเห็นว่า
มันไม่ได้เป็นไปตาม pattern ข้างบนนี้
สมมติเรามี file $RAILS_ROOT/app/model/activity.rb
class Activity < ActiveRecord::Base
end

ลองสั่ง script/console ดู

$ script/console
Loading development environment.
>> Activity.new
=> #<Activity:0x245efcc @new_record=true, @attributes={"name"=>nil}>

คำถามก็คือ rails ไป load activity.rb มา โดยเราไม่ต้องสั่ง require สักแอะได้อย่างไร

คำตอบอยู่ใน file ที่ชื่อ
/..verylong../active_support/dependencies.rb
ภายในมี method อยู่ตัวหนึ่งที่ชื่อ const_missing

สำหรับคนที่ไม่คุ้นกับ method นี้
method นี้เป็น method ใน class Module
หน้าที่ method นี้ก็คือ ในกรณีที่มีการเรียกใช้ Constant ที่ไม่มีอยู่จริง
>> class Module
>> def const_missing(name)
>> puts 'hi'
>> end
>> end
=> nil
>> Bunn
hi
=> nil
>> P
hi
=> nil
>>


ใน dependency.rb ก็มีการ override method นี้ดังนี้
class Module #:nodoc:
# Rename the original handler so we can chain it to the new one
alias :rails_original_const_missing :const_missing

# Use const_missing to autoload associations so we don't have to
# require_association when using single-table inheritance.
def const_missing(class_id)
file_name = class_id.to_s.demodulize.underscore
file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}"
begin
require_dependency(file_path)
...
long long code for error case

Magic ก็คือ require_dependency ที่ rails เรียกใช้
,require_dependency คือ function ที่ rails สร้างขึ้นมา
เพื่อใช้แทน require โดยมีข้อแตกต่างคือ
ในกรณีของ development environment
module, class ที่ load โดย require_dependency จะถูก
flush ออกไปหลังจากที่จบ request แล้ว
ทำให้ developer สามารถแก้ไข source code ได้อย่างสบายใจ
ไม่ต้อง restart web server ใหม่ทุกครั้งหลังจากแก้ไข code

ส่วนประโยค file_name = class_id.to_s.demodulize.underscore
ก็คือการ solve หาชื่อ file ที่จะ require
โดยมีหลักประมาณว่า
"Activity" -> 'activity'
"OrderItem" -> 'order_item'

Related link from Roti

Friday, March 02, 2007

Ruby 'require'

คำสั่งง่ายๆคำสั่งหนึ่งที่หลายคน (รวมผมด้วย) มองข้ามไปก็คือ require
คำว่ามองข้าม หมายถึง ใช้พอเป็น, แต่่ไม่สนใจกลไกของมัน
ถ้ามีปัญหาก็มั่วอย่างเดียว จนกว่าจะใช้ได้

วันนี้ก็เลยนั่งสะสาง ความเข้าใจเกี่ยวกับเจ้า require

require คือกลไกการ include source code อย่างหนึ่งของ ruby
มีคำสั่งที่เหมือนกันอีกคำสั่งหนึ่งก็คือ load
สิ่งที่ต่างกันก็คือ ถ้าสั่ง require ซ้ำๆกัน
มันจะ load แค่ครั้งแรกครั้งเดียว

ลองสร้าง file p.rb ที่ current directory
a = 1
b = 2
@c = 3
def d
"hi"
end

จากนั้นก็เรียก irb แล้วสั่ง
$ irb
>> require 'p'
=> true
>> require 'p' # load ครั้งที่ 2 return false เพราะถือว่า load ไปแล้ว
=> false

เงื่อนไขอย่างหนึ่งของการ require ก็คือ
local variable ที่อยู่ใน p.rb จะไม่ตามมาด้วย
>> require 'p'
=> true
>> a
NameError: undefined local variable or method `a' for #
from (irb):3
>> b
NameError: undefined local variable or method `b' for #
from (irb):4
>> @c
=> 3
>> d
=> "hi"


คำถามถัดมาก็คือ แล้วเวลาสั่ง require 'p'
มันมองหา file ที่ชื่อ p.rb ที่ไหนบ้าง,
ruby มีตัวแปร array อยู่ตัวหนึ่งที่ชื่อ $LOAD_PATH
ซึ่งตัวแปรนี้มี short-name อีกชื่อหนึ่งคือ $:
(คนไม่ชอบ perl เห็นแล้วคงร้องยี้)
>> $LOAD_PATH
=> ["/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/bin",
"/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/.",
"/usr/local/lib/ruby/site_ruby/1.8",
"/usr/local/lib/ruby/site_ruby/1.8/powerpc-darwin8.7.0",
"/usr/local/lib/ruby/site_ruby", "/usr/local/lib/ruby/1.8",
"/usr/local/lib/ruby/1.8/powerpc-darwin8.7.0", "."]
>> $:
=> ["/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/bin",
"/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/.",
"/usr/local/lib/ruby/site_ruby/1.8",
"/usr/local/lib/ruby/site_ruby/1.8/powerpc-darwin8.7.0",
"/usr/local/lib/ruby/site_ruby",
"/usr/local/lib/ruby/1.8",
"/usr/local/lib/ruby/1.8/powerpc-darwin8.7.0", "."]
>>

ดังนั้นถ้าเราอยาก add load path ส่วนตัวของเรา
เราก็สามารถ append path เข้าไปใน ตัวแปรนี้ได้เลย
>> $LOAD_PATH << '/tmp' 


ที่นี้ลองไปดูที่ rails บ้าง
rails เตรียม custom configuration ให้เรา set load_path
ไว้ใน file $RAILS_ROOT/config/environment.rb
Rails::Initializer.run do |config|
config.load_paths += %W( #{RAILS_ROOT}/extras )
end

การทำงานภายใน ก็คือมันไปเรียกใช้ตัวแปร $LOAD_PATH
แต่แทนที่จะ append ไปข้างหลัง $LOAD_PATH array
มันใช้วิธีไส่ไว้ข้างหน้าแทน เพราะกลไกการหามันจะเริ่มจาก
หน้าไปหลัง การใส่ไว้ข้างหน้า ทำให้สามารถ override file ข้างหลังได้
# Set the <tt>$LOAD_PATH</tt> based on the value of
# Configuration#load_paths. Duplicates are removed.
def set_load_path
configuration.load_paths.reverse.each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
$LOAD_PATH.uniq!
end

Related link from Roti

Thursday, March 01, 2007

Linux บน Mainframe

ปกติเห็นใครใช้ Linux ก็จะดีใจ
เพราะเห็นว่ามันออกไปในแนวทางพึ่งตนเองดี

แต่วันนี้ได้ยินข่าวนี้แล้วก็รู้สึกแปลกๆ
(แปลก ตรงว่า ไม่ค่อยเชื่อใจว่าจะใช้งานได้คุ้ม ตามราคาที่จ่ายไป)

สัญญา สปส. 2900 ล้านที่เซ็นไป เขาเริ่มทำงานแล้ว (ใครจะหาว่าสัญญามีกลิ่นตุๆ ก็ว่ากันไป)
โดยมีการยก mainframe เข้าไปติดตั้ง
เพื่อพร้อมที่จะทำการขึ้นระบบแรก
mainframe ที่ว่านี้จะมีแต่ linux ล้วนๆ
โดยทำเป็น virtual machine
ซึ่งคาดเดาว่าจะช่วยลด cost ที่ต้องมี server กระจัดกระจายได้
+ share กันไช้ cpu ทำให้รวมๆแล้วมีจำนวน cpu ที่ต้องใช้ลดลง

เมื่อมี hardware ก็ต้องมี software
ระบบแรกที่จะส่งมอบ phase แรก
ก็คือระบบ "ประชาสัมพันธ์ข่าวสารกระทรวง"
ว้าว ระบบนี้มีบุญจริงๆ ได้ run บน mainframe ด้วย

เอ้ามองโลกแง่ดีหน่อย
มีเวลา 5 ปี ที่จะหาระบบดีๆ ขึ้นไป run
เพื่อให้คุ้มกับค่างบ 2900 ล้าน

Related link from Roti

Friday, February 23, 2007

HylaFax

Fax ที่ทำงานเสีย
ก็เลยได้ฤกษ์หา fax server มาใช้แทน
จัดการหาเครื่อง pc เก่าๆ กับ model เก่าๆมาตัว
ลง ubuntu server
สั่งติดตั้ง hylafax-server
เรียก faxsetup แล้วก็ yes ไปเรื่อยๆ
แว๊บเดียวก็เรียบร้อย

ส่วนที่ยาก กลับกลายเป็นว่า
fax-client ไม่สามารถ download fax file ได้
เกิด error
550 : Operation not permitted.

นั่งหา, มั่ว อยู่นาน
ผ่านไป 2 ชั่วโมง
ได้ข้อสรุปว่า
permission ของ file มันเป็น 600 อยู่
ทำให้ ftp มัน load ไม่ได้
ก็เลยไปแก้ config file ของ hyla
กำหนด default สิทธิ file ให้เป็น 666 แทน
ส่วนของเก่าที่รับมาแล้ว ก็ใช้ command-line สั่งแก้ไป

เฮ้อ config พวกนี้
มันมีเรื่องเล็กเรื่องน้อยแบบนี้ซ่อนอยู่เสมอ

Related link from Roti

Wednesday, February 21, 2007

ขี่จักรยานยามมืด

เห็นเจ้า Rerng@IT ออกไปปั่นจักรยานมาหลายคืนแล้ว
ชักอิจฉา
เมื่อคืนเลยออกไปบ้าง
เส้นทางจากรัชโยธินเลือกวิ่งไปทางอนุสาวรีย์หลักสี่
แล้ววกกลับมาทางวิภาวดี



ขี่ตอนกลางคืน ก็สบายๆดีแฮะ
(ปกติจะซาดิสต์ ชอบขี่ตอนแดดร้อนๆ)

พวก Rerng@IT + น้องโบ้ เขาขี่แบบกระโดด + โลดโผน
ผมใช้เสือหมอบ และขี่กระโดดไม่เป็น
ก็เลยขับตรงๆบนถนน ไม่ซอกแซก
สังเกตเส้นทางใน map สิ ตรงแหน๊ว
(เส้นทางขี่ก็บ่งบอกบุคลิกผู้ขี่ได้เหมือนกัน)

ระหว่างทางมี Amazing อยู่อย่าง
ก็คือเจอน้ำตก ต้องขี่ข้าม
จริงๆ น้ำตก ไหลโกร๊กเลย
ขนาดเท้าอยู่บนบันไดจักรยาน
น้ำมันยังพัดมาโดนเปียกได้เลย
คิดดูว่าน้ำสูงขนาดไหน

เฉลย น้ำตกนั้นคงเป็นท่อประปาแตก
แต่ต้องเป็นท่อใหญ่มากเลย
จึงจะทำให้น้ำไหลได้ขนาดนั้น

กลับมาถึงบ้าน
ปรากฎว่า ล้อเสือหมอบคดเลย
เซ็งเลย โชคดีมีล้ออะไหล่เปลี่ยน
วันนี้เลยขี่มาทำงานได้

ปล. ฝากถาม doc หรือผู้รู้หน่อย
ล้อคดนี่เขาซ่อมกันหรือเปล่า
หรือเปลี่ยนใหม่อย่างเดียว

Related link from Roti

Monday, February 19, 2007

Any bug here?

Ridiculous fish เขียน post ดีๆอีกแล้ว
คราวนี้เขาแสดงให้ดูว่า
code ข้างล่างนี้มี bug ได้อย่างไร
(เพื่อความสนุกในการอ่าน, ให้พยายามไล่ดู code ก่อน
ว่ามันจะมี bug v2 > v1 ได้อย่างไร)
unsigned variable1 = 0;
unsigned variable2 = 0;

#define ITERATIONS 50000000

void *writer(void *unused) {
for (;;) {
variable1 = variable1 + 1;
variable2 = variable2 + 1;
}
}

void *reader(void *unused) {
struct timeval start, end;
gettimeofday(&start, NULL);
unsigned i, failureCount = 0;
for (i=0; i < ITERATIONS; i++) {
unsigned v2 = variable2;
unsigned v1 = variable1;
if (v2 > v1) failureCount++;
}
gettimeofday(&end, NULL);
double seconds = end.tv_sec + end.tv_usec / 1000000. - start.tv_sec - start.tv_usec / 1000000.;
printf(”%u failure%s (%2.1f percent of the time) in %2.1f seconds\n”,
failureCount, failureCount == 1 ? “” : “s”,
(100. * failureCount) / ITERATIONS, seconds);
exit(0);
return NULL;
}

int main(void) {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, writer, NULL);
pthread_create(&thread2, NULL, reader, NULL);
for (;;) sleep(1000000);
return 0;
}

Related link from Roti

Saturday, February 17, 2007

T-Bone in TK Park

วันนี้พาคุณลูกไปเที่ยว TK park
บังเอิญโชคดีว่า TK park เชิญ T-Bone มาแสดงพอดี
เลยมีบุญได้ฟังการแสดงสดอย่างไกล้ชิด
โดยไม่ต้องไปเสียสุขภาพ อดหลับอดนอนไปรอฟังที่ Saxophone
เนื่องจากสถานที่ไม่ใหญ่โต คนก็ไม่เยอะ
เลยได้นั่งฟังอย่างไกล้ชิด

ผมไม่ได้ฟังการแสดงสดมานานมากแล้ว
เลยรู้สึกดีเป็นพิเศษ
ส่วนคุณลูกด้วยความที่เล่นมาทั้งวัน
พอฟังได้เพลงครึ่งก็ซบพ่อหลับไป
(beat ขนาดนั้น, เสียงก็ดัง, มันยังหลับได้อีก)

ระหว่างเล่น ก็มีเสริมความรู้ให้ด้วยนะ
เช่น อธิบายว่า สกากับเร็คเก้ ต่างกันอย่างไร
มีความเป็นมาอย่างไร
เสร็จแล้วก็เล่นเพลงเดียวกันเปรียบเทียบให้ดู
เริ่มจากสไตล์ jazz ก่อน
จากนั้นก็ลองเล่นเป็น ska
สุดท้ายก็เล่นเป็นเร็คเก้

ช่วงกลางๆมีเล่นเพลง เธอเห็นท้องฟัานั่นไหม
ได้ยินแล้วรู้สึกตัวเองแก่ทันที
ในหัวมันมีประโยคหนึ่งผุดขึ้นมา
"กาลครั้งหนึ่ง นานมาแล้ว..."

Related link from Roti

Friday, February 16, 2007

Spring return LinkList

อาจารย์มะนาว พึ่งเขียน ชื่นชอบสุดๆ
ในนั้นมี link เรื่อง The departure of the hyper-enthusiasts
ที่วิจารณ์พวก ruby + rails ที่กำลังสนุกกันมากไปหน่อย

เรื่องศาสนา กับความชอบ ผมไม่สนใจเท่าไร
ที่สนใจก็คือใน post มี quote ที่พูดถึง container
ว่าทำไม interface ใน List ของ java ไม่มี getLast() แบบที่ ruby มีใน Array

วันนี้อ่าน Sanjiv Jivan 's Blog
เจอ post know how to iterate พูดถึง spingดframework jdbc helper
มัน return List ที่เป็น linkList ออกมา
ถ้าคนไม่รู้ ดันไปใช้คำสั่งพวก
for (int i = 0; i< list.size; i++) {
X x = (X) list.get(i)
}

ถ้า data เยอะมากๆ , แตะหลักแสนเมื่อไร รับรองว่าเจอฝันร้ายด้าน performance แน่นอน

ใน post ได้อ้างถึง benchmark
ลองดูเวลาระหว่าง ArrayList กับ LinkedList

--------------------- ArrayList ---------------------
size add get set iteradd insert remove
10 121 139 191 435 3952 446
100 72 141 191 247 3934 296
1000 98 141 194 839 2202 923
10000 122 144 190 6880 14042 7333
--------------------- LinkedList ---------------------
size add get set iteradd insert remove
10 182 164 198 658 366 262
100 106 202 230 457 108 201
1000 133 1289 1353 430 136 239
10000 172 13648 13187 435 255 23


ส่วนวิธีที่ถูกต้องในการ access LinkList คือใช้ iterator

Related link from Roti

Thursday, February 15, 2007

ProjectEuler

พักนี้ใน mailing list ของ Haskell
มี newbie ถามประเด็นคำถาม เกี่ยว program
ที่เกี่ยวข้องกับโจทย์ทางคณิตศาสตร์ ถี่ขึ้น (ถี่ในที่นี้ หมายถึงประมาณ 1-3 ฉบับต่อเดือน)
เมื่ออ่านรายละเอียดก็พบว่า โจทย์นั้นมีที่มาจาก ProjectEuler
ซึ่งเป็น site ที่มีการตั้งโจทย์ทางคณิตศาสตร์ แล้วให้เขียนโปรแกรม solve หาคำตอบ

หลังจากตาม mailing list ไปที่ site นี้เป็นครั้งที่ 3
ผมก็เลยตัดสินใจลองเล่นดูบ้าง
เพื่อจะได้ฝึก programing ด้วย haskell และ ฝึกใช้สมองที่มีจุดอ่อนในส่วนคณิตศาสตร์

หลังจาก register และ login แล้ว
จึงรู้ว่ามันมีหน้าสถิติให้ดูด้วย
ซึ่งสถิติที่ผมสนใจคือตัวนี้



จากจำนวน active user 2242 คน
มีคนระบุสัญชาติ 1555 คน
เป็นอเมริกันเสีย 338 คน -> 20 %
รองลงไปคือ สวีเดน 146 คน

ลองไล่ดูกลุ่มรองลงไปบ้าง

อังกฤษ 82
เยอรมัน 84
อินเดีย 85
แคนาดา 74

กลุ่มรองลงไปอีก

โปรตุเกส 52
ออสเตรเลีย 48
รัสเซีย 44
เนเธอร์แลนด์ 39

พวกข้างบนนี่ไม่ทำให้ผมแปลกใจอะไร
แต่ชาติที่ทำให้ผมแปลกใจ ก็คือ เวียดนาม
คือมีจำนวนคนที่ active อยู่ถึง 28 คน
เทียบกับเพื่อนบ้านเรา
อินโดเนเซีย 8
มาเลเซีย 2
สิงคโปร์ 2

ถ้าผมมองว่า คนที่เข้ามาเล่นใน site นี้
เป็นกลุ่มคนที่เป็น self learning
มีแรงจูงใจจากภายในของตนเอง
เพราะว่า site นี้เขาตั้งโจทย์เฉยๆ, มีช่องให้ใส่ตัวเลขเพื่อตรวจคำตอบ
และไม่มีเฉลยให้ดู

จำนวนคนที่มาจากเวียดนาม คงบ่งบอก อนาคต บางอย่างให้เห็นได้เลาๆ

Related link from Roti

Tuesday, February 13, 2007

Version Control

เห็นข้อมูลหนังสือ Ship It เขาอ้างว่า
ที่อเมริกา ยังมี project ที่ไม่ได้ใช้พวก SCM อยู่ประมาณ 40 %
อืมม์อยากรู้จังว่าบ้านเรา มีกี่เปอร์เซนต์
คงจะมากกว่า 40 อย่างแน่นอน

ในส่วนตัวผมในแง่งานที่บริษัทฯ, ใช้ SCM มาได้หลายปีอยู่
โดยเริ่มจาก CVS แล้วก็เปลี่ยนมาเป็น SVN
สถานะปัจจุบัน "ชีวิตนี้ขาด SCM ไม่ได้แล้ว"
(prototype บางอัน ทำแค่คนเดียว ยังอุตส่าห์ใส่ SCM ด้วย)

แน่นอนว่าหลังจากเปลี่ยนมาใช้ SCM แล้ว
คุณภาพชีวิตย่อมดีขึ้นแน่นอน
แต่ก็ยังมีประเด็นเล็กๆน้อยที่ยังต้องตามแก้ไขต่อ เช่น

  • การใช้อะไรที่มากกว่า trunk
    ข้อนี้พบว่า developer ที่บริษัทผม ใช้ SCM ในการ develop main trunk เพียงอย่างเดียว
    ไม่มีใครเคยใช้ branches แม้ว่าสภาพงานบางงานเอื้อให้เกิด branch เสียเหลือเกิน
    (โปรเจคที่บริษัทฯ ส่วนใหญ่ไม่มี release
    เพราะเป็นโปรเจคแบบลูกค้าคนเดียว
    แถมยัง evolve ไปเรื่อยๆด้วย)
  • นิสัยในการแก้ไข code
    มี developer หลายๆคนที่ยังติดความเคยชินเก่าๆ ในการแก้ไข source code อยู่ เช่น
    สมมติ เรามี
    x = 1.2 * y

    ถ้ามีการแก้ไขขึ้นมา เช่นเปลี่ยน constant ที่ใช้คูณ
    แทนที่จะแก้ตรงๆ ก็ดันไปทำแบบนี้แทน
    // x = 1.2 * y
    x = 1.3 * y

    คือยังทำเหมือนกับไม่ได้ใช้ SCM
    ส่งผลให้ทั้ง source code และผลลัพท์ที่เกิดจากการ diff version, ดูรกหูรกตา
  • วิธีการ commit
    บางคนยังชอบไล่ commit ที่ละ file
    แทนที่จะทำ commit เป็น set ของการเปลี่ยนแปลง
    (ซึ่งจะทำให้การไล่อ่าน history หรือ ตรวจติดตาม change มันง่ายกว่า)
  • การใส่คำอธิบาย ในส่วนของการ commit
    อันนี้ก็คงประเด็นต่อเนื่องมาจาก comment
    ก็คือ ในเมื่อ comment ใน code ยังใส่น้อยอยู่เลย
    นับประสาอะไรกับ comment ใน changeset

Related link from Roti

Monday, February 12, 2007

กรรม เป็นผลจากการกระทำ

ยิ่งเขียนโปรแกรม ผมก็ยิ่งซึ้งกับเรื่อง "กรรม"
ยิ่ง maintain โปรแกรมเก่าเรามากเท่าไร ก็ยิ่งซึ้งมากขึ้นเท่านั้น
quick fix นี่ตัวดีเลย,

ยกตัวอย่าง quick fix หนึ่งของผม
สมัยเมื่อนานมาแล้ว
มีโปรเจคอยู่โปรเจคหนึ่งซึ่งเป็นโปรเจค ลูกเมียน้อย
(หมายความว่า ไม่ได้ถูกให้ความสำคัญทั้งจากคนจ้างและคนรับจ้าง)
ซึ่งผมต้องทำ prototype ไปให้ user ใช้
มีอยู่หน้าจอหนึ่ง มันขาด field ไป field หนึ่ง
เผอิญใน table มันมี field หนึ่งที่พึ่งถูกตัดทิ้งไป
ก็เลยหยิบเอามาใช้ก่อนเลย ทั้งที่ naming,datatype มันคนละเรื่องคนละราวเลย

เผลอแป๊บเดียว โปรเจคลูกเมียน้อย
เกิดกลายเป็นทายาทมรดกร้อยล้านขึั้นมา
data วิ่งเข้าไปเป็นหลาย ล้าน record แล้ว
โปรแกรมก็มีหน้าจอเกินหลักร้อยไปเยอะแล้ว
การ refactor เปลี่ยนชื่อ column ใน table กลายเป็นเรื่องใหญ่

ช่วงนี้มีการ upgrade ตัวโปรแกรม
จากหน้าจอ terminal ขึ้นมาเป็น web application
ชื่อ column ที่ quick fix ไว้ก็เลยโผล่ขึ้นมาหลอกหลอน

จริงๆกรรมนี้ถือเป็นกรรมเล็กๆน้อยๆ
กรรมใหญ่กว่านั้นนี่ยังมีอีกหลายนัก

Note: เรื่อง กรรม นี้ คงใช้ได้กับบุคคลที่ไม่ได้เปลี่ยนงานบ่อย
ส่วนพวกเปลี่ยนงานหลังโปรเจคจบ ก็สบายตัวไป
(ไปได้กรรมอีกประเภทคือ ไม่เคยได้อยู่ดู feedback
ซึ่งถือว่าเป็นประสบการณ์ที่ช่วยพัฒนาตัวเองดีนักแล)

Related link from Roti