Friday, July 13, 2007

Lists and Lists

Lists and Lists
อาฮ้า เกมส์นี้เขียนขึ้นเมื่อปี 1996 เป็นเกมส์ประเภท IF (interactive fiction)
แต่จุดเด่นของเกมส์นี้ก็คือ มันเป็น programming tuturial ด้วย
ใครอยากรู้จัก scheme ลองเล่นเกมส์นี้ดูได้

Note: ที่ลง link ไว้เป็น java applet ที่เข้าเล่นเกมส์เลย
แต่ถ้าใครอยาก download ไว้เล่นที่เครื่องตัวเอง
ต้องไปที่นี่ Link

Related link from Roti

Thursday, July 12, 2007

FlexUnit

เมื่อวานนั่งทดลองเขียนเกมส์ Hangman บน Flex ดู
สิ่งที่ขัดอกขัดใจก็คือ มันไม่มี Unit Testing มาให้

ซึ่งก็จริงของมันในแง่หนึ่ง ก็คือ
component ที่เป็น UI component
มันทำ testing แบบ Unit ได้ยาก (ก็เลยไม่มีเสียเลย แต่ก็ไปแอบมี third-party ทำ GUI Testing plugin แทน)
แต่เผอิญว่า component ของเรา มันเป็น Non-visual component
และต้องการทดสอบความถูกต้อง ก่อนที่จะ integrate กับ UI component

จากการ search พบว่า มันมี library ที่ชื่อ FlexUnit อยู่เหมือนกัน
(อยู่ใน adobe labs project ด้วย)
ก็เลย load มาทดสอบดู

การใช้งาน ก็คือ สร้าง MXML Application ขึ้นมาตัวหนึ่ง
มีหน้าตาแบบนี้

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:flexunit="flexunit.flexui.*"
creationComplete="onCreationComplete()"
>
<mx:Script>
<![CDATA[
import flexunit.framework.TestSuite;
import hangman.test.GameTest;

// After everything is built, configure the test
// runner to use the appropriate test suite and
// kick off the unit tests
private function onCreationComplete():void
{
testRunner.test = createSuite();
testRunner.startTest();
}

// Creates the test suite to run
private function createSuite():TestSuite {
var ts:TestSuite = new TestSuite(GameTest);
return ts;
}

]]>
</mx:Script>

<!-- flexunit provides a very handy default test runner GUI -->
<flexunit:TestRunnerBase id="testRunner" width="100%" height="100%" />
</mx:Application>


ตัว TestCase ก็เขียนเหมือน XUnit ทั่วๆไป

package hangman.test
{
import flexunit.framework.TestCase;
import hangman.core.Game;

public class GameTest extends TestCase
{
public function testNewGame():void
{
var game:Game = new Game();
game.word = "hello";
assertEquals("_____", game.unreveal);
}

public function testGuess():void
{
var game:Game = new Game();
game.word = "hello";
game.guess("h");
assertEquals("h____", game.unreveal);
game.guess("l");
assertEquals("h_ll_", game.unreveal);
}

public function testGuessMistake():void
{
var game:Game = new Game();
game.word = "hello";
game.guess("a");
assertEquals(1, game.mistake);
game.guess("b");
assertEquals(2, game.mistake);
assertEquals("_____", game.unreveal);
}
}
}


เวลา run ก็ใช้ได้ผลลัพท์ออกมาใน browser แบบนี้



ข้อสังเกต
1. unit test ไม่ได้ integrate ใน flex-builder ทำให้เราไม่สามารถกระโดดจากผลลัพท์
ไปยัง source code ได้โดยตรง
2. กรณีที่มี testcase, flex ยังไม่มี best-practice ของการจัด Project Layout
ว่าควรจะเขียน testcase แยกเป็นอีก project หรือ รวมไว้ใน project เดียวกัน
ถ้ารวมจะจัด package หรือ folder structure อย่างไรดี

Related link from Roti

Tuesday, July 10, 2007

ทดลอง implement widget บน GWT

ช่วงนี้ผมกำลังทดลองเขียนโปรแกรม hangman
เพื่อจะได้ทดสอบ framework ต่างๆเปรียบเทียบกัน
โดยต้นแบบของเกมส์ ได้มาจาก code ตัวอย่างใน cincom smalltalk
ที่ใช้ seaside เป็น framework

บน game Hangman, user สามารถกดเลือกตัวอักษรที่จะทายได้
โดยตัวอักษรพวกนี้ จะ render เป็น hyperlink ไว้
และเมื่อ user กดเลือกไปแล้ว, hyperlink นั้นก็จะกลายเป็น text ธรรมดา
เพื่อเป็น feedback ให้ user รู้ว่าได้ทำการเลือกไปแล้ว



ผมลอง implement feature ที่ว่าเป็น widget ใน GWT ดู
เกริ่นก่อนว่า, ใน GWT ก็มี widget ประเภท hyperlink ให้ใช้
แต่มีข้อจำกัดว่า ไม่สามารถทำให้มันกลายเป็น text ธรรมดาได้
เราก็เลยต้อง customize ทำ hyperlink แบบพิเศษของเราขึ้นมาเอง

ทางเลือกในการ implement hyperlink แบบพิเศษของเรา มีอยู่ 2 ทางคือ
1. extends จาก hyperlink ของเดิม แล้ว overide method บางตัว เพื่อปรับพฤติกรรม
2. เขียนขึ้นมาเอง โดยไม่ยุ่งกับ HyperLink widgetเลย
ผมเลือกใช้วิธีที่ 2 เพื่อจะได้ทำความเข้าใจกับกลไกการทำงานของ GWT มากขึ้น

เริ่มแรกสุด ก็คือเลือก extend widget ของเราจาก FocusWidget,
โดย FocusWidget คือ base class สำหรับ widget ที่สามารถ click
หรือเรียกใช้ผ่าน access key ได้
public class Letter extends FocusWidget {

}

ขั้นถัดมา ก็คือ implement ส่วนที่เกี่ยวกับ การ render
วิธีการที่ GWT ใช้ render widget ก็คือ render เป็น DOM element
public class Letter extends Widget {
private Element anchorElem;
private char ch;

public Letter(char ch) {
this.ch = ch;
setElement(DOM.createSpan());
DOM.appendChild(getElement(), anchorElem = DOM.createAnchor());
DOM.setInnerText(anchorElem, Character.toString(ch));
DOM.setElementProperty(anchorElem, "href", "#");
}
}

ผลลัพท์ที่ได้จากข้างบน ก็คือเราจะได้ html element ดังนี้
<span><a href="#"></a></span>


ข้อสงสัยก็คือ ถ้า render แค่นี้แล้ว GWT จะรับ javascript event onclick ได้อย่างไร
คำตอบก็คือ ถ้าเราไปไล่ code ของ FocusWidget ดู
เราจะเห็นว่าหลังจากที่เราสั่ง setElement
code ใน FocusWidget จะทำการเรียกใช้
sinkEvents(Event.ONCLICK | Event.FOCUSEVENTS | Event.KEYEVENTS);

ซึ่งเป็นการระบุว่า widget เรา สามารถรับ event กลุ่ม click, focus, keyevent ได้
โดย callback ที่ต้อง implement เพื่อรับ event ก็คือ method onBrowserEvent
ในกรณีของเรา FocusWidget มีการ implement onBrowserEvent ไว้ดังนี้แล้ว
public void onBrowserEvent(Event event) {

switch (DOM.eventGetType(event)) {
case Event.ONCLICK:
if (clickListeners != null) {
clickListeners.fireClick(this);
}
break;
...
}
}

สิ่งที่เราต้องการ overide ก็คือ เมื่อเกิด clickEvent แล้ว
เราต้องการให้ hyperlink ของเรา click อีกไม่ได้
ซึ่งทำง่ายๆโดย เอา attribute href ออกจาก anchor tag
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
if (DOM.eventGetType(event) == Event.ONCLICK) {
DOM.removeElementAttribute(anchorElem, "href");
DOM.eventPreventDefault(event);
}
}


เมื่อได้ตัวอักษรเดี่ยวๆแล้ว ก็มาลองทำ widget ที่ช่วย render ชุดตัวอักษรบ้าง
โดย widget นี้เราจะนำเอา Letter widget ที่เราพึ่งทำไปมาเรียงต่อๆกัน
ในกรณีนี้ widget ที่เราจะเขียน จะ extend จาก Composite
public class Letters extends Composite implements ClickListener {

private Letter[] letters;

public Letters(String chars) {
FlowPanel panel = new FlowPanel();
char[] chs = chars.toCharArray();
letters = new Letter[chs.length];
for (int i = 0; i < chs.length; i++) {
letters[i] = new Letter(chs[i]);
letters[i].addClickListener(this);
panel.add(letters[i]);
panel.add(new Space(1));
}
initWidget(panel);
}

...

}


ความรู้สึกที่ได้จากการทดลอง implement widget ใน GWT ก็คือ
1. ความรู้สึกก้ำกึ่ง ระหว่างความชอบ กับ ไม่ชอบ
เนื่องจาก บางอย่าง ถ้าเราเขียนด้วย javascript โดยตรง มันจะง่ายกว่า
แต่บางอย่าง เขียนด้วย GWT ก็จะง่ายกว่า (เช่น sinkEvent)
2. GWT มันไม่ support feature ของ Java 1.5
ทำให้รู้สึกเย่นเย้อเวลาเขียนบางอย่าง เช่นพวก for loop
หรือต้องใส่ casting เวลา access collection

Related link from Roti

Monday, July 09, 2007

LEL

โปรเจค Profligacy เป็นโปรเจค ที่ช่วยให้เรา paint swing component
โดยใช้ JRuby เข้ามาช่วย
สิ่งที่น่าสนใจในโปรเจคนี้ก็คือแนวคิดเรื่อง LEL (Layout Expression Language)
โดยนำแนวคิดของเรื่อง table format ในพวก wiki มาผสมกับ Constraint programming
ก็เลยได้ออกมาเป็นวิธีใหม่ในการวาง layout

ลองดูตัวอย่าง code
require 'profligacy/swing'
require 'profligacy/lel'

module Test
include_package 'javax.swing'
include Profligacy

layout = "
[ label_1 | label3 ]
[ (300,300)*text1 | (150)people ]
[ <label2 | _ ]
[ message | buttons ]
"


ui = Swing::LEL.new(JFrame,layout) do |c,i|
c.label_1 = JLabel.new "The chat:"
c.label2 = JLabel.new "What you're saying:"
c.label3 = JLabel.new "The people:"
c.text1 = JTextArea.new
c.people = JComboBox.new
c.message = JTextArea.new

c.buttons = Swing::LEL.new(JPanel, "[send|hate|quit]") do |c,i|
c.send = JButton.new "Send"
c.hate = JButton.new "Hate"
c.quit = JButton.new "Quit"
end.build :auto_create_container_gaps => false
end

ui.build(:args => "Simple LEL Example")
end


จะเห็นว่า main format เขาวางไว้แบบนี้
[ label_1         | label3      ]
[ (300,300)*text1 | (150)people ]
[ <label2 | _ ]
[ message | buttons ]


คำอธิบายก็คือ layout จะเป็น grid ที่มี 4 แถว 2 column,
< หมายถึง ชิดซ้าย
> หมายถึง ชิดขวา
* คือ expand ให้เต็มพื้นที่
_ หมายถึง empty cell
(150) หมายถึง กว้าง 150 pixel
(300,300) กว้าง 300 ยาว 300 pixel

ผลลัพท์ที่ได้ ลองตามไปดูที่นี่
http://ihate.rubyforge.org/profligacy/images/sample_lel_gui_nested.png

Related link from Roti

Thursday, July 05, 2007

Squeak Tutorial

หัดเล่น squeak + seaside มาระยะหนึ่งแล้ว
อุปสรรคอย่างแรกสุดในการใช้ ก็คือ สภาพแวดล้อมในการพัฒนา
ที่ค่อนข้างบิดมุมไปจากสิ่งที่เคยคุ้นเคยมา
แถมยังหา tutorial ที่พูดถึงสิ่งที่เราต้องการรู้ได้ยาก
ส่วนใหญ่จะพูดถึงแต่เรื่อง Graphpics (Morphic)

โชคดีที่ใน version 3.9 นี้ มี tutorial ที่ดีมากออกมาด้วย
http://squeak.preeminent.org/tut2007/html/index.html
หลังจากลองเล่นไป section หนึ่ง
ก็ยิ่งพบว่าสภาพแวดล้อมการพัฒนาแบบนี้ มันเจ๋งใช้ได้เลย
จากที่่เคยได้ยินว่า eclipse ได้รับอิทธิพลจาก smalltalk IDE
ก็เห็นชัดเจนแจ่มแจ๋ว
หรือที่เคยรู้ว่า xunit ก็ได้อิทธิพลจาก sunit ของ smalltalk
พอมาใช้ sunit (แม้จะใช้แค่นิดเดียว) แต่ก็รู้สึกถึงความ powerful ของมันได้

อุปสรรคที่สองที่พบเวลาหัดใช้ squeak ก็คือ ตัว image ที่ให้มา
มันไม่เหมาะกับการเป็น developer tool สักเท่าไร
เช่น ไม่มี syntax highlight, ไม่มี code completion
การแก้ไขก็คือ ควรจะ download
เอา image ที่เตรียมไว้สำหรับ developer มาใช้

Related link from Roti

Monday, July 02, 2007

Image in seaside

วันก่อนนั่งแกะ seaside ซึ่งเป็น web application framework ของค่าย smalltalk
ไปเจอ code ส่วนที่จัดการกับ static file เช่นพวก image, css
หน้าตาเป็นแบบนี้

WAFileLibrary subclass: #WAFileLibraryDemo
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Seaside-Examples-Misc'

mainJpg
^ #(255 216 255 224 0 16 74 70 73 70 0 1
1 1 0 72 0 72 0 0 255 225 0 22 69 120 105
102 0 0 77 77 0 42 0 0 0 8 0 0 0 0 0 0 255
219 0 67 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
...)


อืมม์น่าสนใจวิธีการเก็บ static file มาก
เล่นเก็บเป็น bytearray ใน instance variable เลยแฮะ
ที่เป็นเช่นนี้เพราะโลกของ smalltalk มันเป็นโลกของ virtual machine
ที่มีแต่ object เวียนว่ายตายเกิดในนั้น
การเก็บข้อมูลในรูปแบบนี้ ก็สวยงามไปอีกแบบ

ต่อจากนั้น ก็เลยไปตามหาเอกสารอ่านดู
แน่นอนว่า seaside ของแท้
เอกสารถึงจะค้นใน google ก็ไม่มีให้ดู
ต้องตามไปดู code อย่างเดียว
โชคดีที่ code ที่ port ไปลง cincom smalltalk มีอธิบายไว้ละเอียด

ตัว class ที่รับผิดชอบในการเก็บ static content มีชื่อว่า WAFileLibrary
วิธีที่นิยมใช้กันก็คือ extend ออกมาเป็น class ของเราต่างหาก
จากนั้นก็สามารถใช้ หน้าจอ config ของ seaside upload content ขึ้นไปได้



เวลา render เป็น image tag ก็อ้างถึงแบบนี้
renderContentOn: html
html image
url: WAFileLibraryDemo / #mainJpg


Note: '/' คือชื่อ class method ส่วน #testJpg คือ symbol ที่แทนรูปที่ต้องการ
มี pattern คือ ชื่อ file ของเรา + file extension ที่แปลงตัวแรกเป็นตัวใหญ่
test.jpg -> testJpg

Related link from Roti

Sunday, July 01, 2007

Chart with Flex

ในงานล่าสุด UI element ต้องมีการแสดง chart ด้วย
โดยเป็น chart ที่มีลักษณะเป็น dynamic, control จาก javascript ได้
ตอนแรก ก็ใช้ plotkit ซึ่งเป็น opensource
แต่พอเอาไปให้ user ดู ก็เจอติเรื่องความสวยงาม
ซึ่งจริงๆถ้ามีเวลา ก็อาจจะไล่แก้โปรแกรม plotkit ทำสวยได้
แต่เมื่อไม่มีเวลา ก็เลยหาโอกาสเสียตังค์ลองใช้ flex ดู

ความรู้สึกหลังจากได้ทดลองพัฒนา component ดู
พบว่า
1. mxml ดีกว่าที่คิดไว้เยอะ
2. model การ binding parameter ของ component ก็งามดี
3. actionscript เขียนง่ายมาก, ถ้ามี background java + javascript
ก็น่าจะผ่านฉลุย
4. api library ออกแบบไว้ดี, ชอบที่เราสามารถ customize nature ของ widget
ได้ด้วยโดยการ pass customize function เข้าไปเป็น parameter (callback)
5. การ integrate กับ javascript ผ่านทาง ExternalInterface ก็ง่ายดาย
6. ถ้าใช้ SDK พัฒนา จะเบื่อกับ build time ที่นานเหลือเกิน (เดี๋ยวนี้ไม่กี่ิวิ ก็เริ่มทนไม่ได้แล้ว)
7. flex builder มันใช้ eclipse เป็นฐาน, ความคุ้นเคยต่างๆ ก็เลยเต็มร้อย

หน้าตาของ component ที่ทำออกมา



ตัวอย่าง javascript ที่ใช้ provide ข้อมูลให้ chart component
document.chart.setBaseAtZero(true);
datas = [{Year: 2005, ROE: 1000, Revenue: 200},
{Year: 2006, ROE: 500, Revenue: 300},
{Year: 2007, ROE: 300, Revenue: 1000}];

series = [{name: "ROE", displayName: "ROE"},
{name: "Revenue", displayName: "Revenue"}];

document.chart.loadChart("Compare", datas, series, "Year");


ส่วนอันนี้คือ mxml ที่เขียน

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="initApp()"
paddingLeft="0"
paddingBottom="0"
paddingRight="0"
paddingTop="0">

<mx:Script>
<![CDATA[
import mx.charts.CategoryAxis;
import mx.charts.series.ColumnSeries;
import mx.charts.chartClasses.IAxis;

public function initApp():void {
if (ExternalInterface.available) {
ExternalInterface.addCallback("loadChart", loadChart);
ExternalInterface.addCallback("setBaseAtZero", setBaseAtZero);
ExternalInterface.addCallback("setLabelPrecision", setLabelPrecision);
}
}

private function setLabelPrecision(value:int):void {
numberFormatter.precision = value;
}

private function setBaseAtZero(value:Boolean):void {
linearAxis.baseAtZero = value;
}

private function loadChart(description:String, datas:Array, maps:Array, category:String):void {
panel.title = description;
var mySeries:Array = new Array();
for each (var obj:Object in maps) {
var ss:ColumnSeries = new ColumnSeries();
ss.xField = category;
ss.yField = obj.name;
ss.displayName = obj.displayName;
mySeries.push(ss);
}

myChart.series = mySeries;

var cat:CategoryAxis = new CategoryAxis();
cat.categoryField = category;
myChart.horizontalAxis = cat;

myChart.dataProvider = datas;

}

private function labelFunction(labelValue:Object, previousValue:Object, axis:IAxis):String {
return numberFormatter.format(labelValue);
}
]]>



</mx:Script>

<mx:NumberFormatter id="numberFormatter" precision="0"
useThousandsSeparator="true" useNegativeSign="true"/>

<mx:Panel id="panel" title="ColumnChart and BarChart Controls Example"
height="100%" width="100%" layout="horizontal">

<mx:ColumnChart id="myChart" height="100%" width="80%"
paddingLeft="5" paddingRight="5"
showDataTips="true" >

<mx:verticalAxis>
<mx:LinearAxis id="linearAxis" baseAtZero="true" labelFunction="labelFunction"/>
</mx:verticalAxis>

</mx:ColumnChart>

<mx:Legend dataProvider="{myChart}" width="20%" />

</mx:Panel>
</mx:Application>


User confirm เมื่อไร ก็เตรียมเสียตังค์ 900 เหรียญ

Related link from Roti

Monday, June 25, 2007

Lisp in Autocad

เคยสงสัยเหมือนกันว่า ทำไป AutoCAD ถึงใช้ Lisp
Lisp is ideally suited to the unstructured interaction that characterises the design process. Unlike programming languages such as C and FORTRAN, which force one to organise a problem entirely before programming, Lisp encourages exploring various approaches to a problem interactively, exactly as CAD helps a designer.

หลายคนที่เคยเขียนแต่ Java อย่างเดียว
จะจิตนาการถึงข้อนี้ไม่ออก
ส่วนตัวผม (ซึ่งหาเลี้ยงชีพด้วย java เช่นกัน)
พอจะเห็นเค้าลางๆ จากการอ่านหนังสือ AI ของ Norvig
Link
"Why Lisp"

Related link from Roti

fatherhood

หลังจากมีลูกก็คิดว่าตัวเองเปลี่ยนไปหลายอย่าง
วันนี้อ่านเจอ มีคนตั้งสมมติฐานว่า
การตั้งท้องและการคลอดไม่ใช่ว่าจะทำให้ผู้หญิงเปลี่ยนไปเพียงฝ่ายเดียว
ผู้ชาย ก็มีการเปลี่ยนแปลงเกิดขึ้นเช่นกัน
เช่น
1. ช่วงระหว่างการตั้งท้อง ผู้ชายจะน้ำหนักขึ้น 20 %
ว่ากันว่า เก็บไว้เปลี่ยนเป็นพลังงานตอนเลี้ยงลูกอ่อน
(roof: คุณลดน้ำหนักอยู่ ระวังไม่มีแรงเลี้ยงลูกนะ)

2. หลังจากคลอดแล้ว ฮอร์โมน Testosterone จะลดลง 1 ใน 3
ซึ่งมีผลหลายอย่าง เช่น
  • มีความอดทนในการอุ้มลูกได้นานขึ้น
  • ลดความก้าวร้าว แทนที่จะออกไปหาเรื่องแข่งขันกับคนอื่น
    ก็จะสงบเสงี่ยมมากขึ้น (ภาษาอังกฤษเขาใช้คำว่า too busy fighting other men)
  • ความสนใจในหญิงอื่นที่ไม่ใช่ภรรยา จะลดลง
    (จากประสบการณ์ตรง เห็นพ้องกับข้อนี้มาก)

ทั้งหมดนี้ ทำให้คุณพ่อ หันมาสนใจเปลี่ยนผ้าอ้อมให้ลูกมากขึ้น

3. มีการเปลี่ยนแปลงสมองในส่วน prefrontal cortex
ส่งผลให้สมองส่วน planning + memory skill ทำงานได้ดีขึ้น
(ประสบการณ์ตรง: พอมีลูกสองคนจะรู้้สึกว่า ข้อนี้มีประโยชน์มาก)

อ่านต่อได้ใน
http://www.slate.com/id/2168389/nav/ais/

Related link from Roti

Thursday, June 21, 2007

Tapestry5 กับ ภาษาไทย

veer เปิดประเด็นปัญหานี้ขึ้นมา ผมก็เลยไปสรุปเพิ่มเติมมา แบ่งเป็น 3 ประเด็นคือ

update: คุณ pa แจ้งว่า มีการแก้ไข case นี้แล้ว
โดยกำหนดให้ set ค่า encoding ผ่านทาง META tag ใน Html template แทน
ซึ่งจะครอบคลุม case 1.2 และ 2

update(27/06/2007): ทดสอบการแก้ไขที่คุณ pa แจ้งแล้ว พบ bug Tapestry-1605


Note: เนื่องจาก T5 ยังอยู่ในช่วยพัฒนา
วิธีการที่ว่ามานี้ ในอนาคตคาดว่าจะมีการ implement กลไกที่ง่ายกว่านี้

1. ประเด็นแรกก็คือ ถ้าเราใส่ภาษาไทยลงไปใน template file ตรงๆ (แทนที่จะใช้กลไก localize ของ T5)
จะแสดงผลให้ถูกต้องได้อย่างไร

ประเด็นนี้ แยกเป็นประเด็นย่อยได้ 2 เรื่องคือ
1.1 ประเด็นการ parse template file ด้วย encoding ที่ถูกต้อง
ถ้าเราไปแกะ code ของ tapestry ดู จะเห็นว่า คนที่รับผิดชอบในการ
parse template ก็คือ TemplateParserImpl
ซึ่งภายในจะใช้กลไก org.xml.sax.helpers.XMLReader ในการ parse
ดังนั้น ปัญหาในจุดนี้ น่าจะแก้ได้โดยการระบุ encoding บนหัว template file

<?xml version="1.0" encoding="UTF-8"?>

Note: ผมไม่เจอปัญหานี้ เนื่องจาก enviroment ผม มี default locale เป็น UTF-8 อยู่แล้ว

1.2 ประเด็นการของ print output ออกไปยัง response stream
ด้วย encoding ที่ถูกต้อง
จากข้อ 1.1 หลังจากได้ character ที่ถูกต้องแล้ว
ก็ต้อง print ออกไปให้ถูก encoding ด้วย
ตรงนี้ เห็นมีพูดถึงใน mailing-list, เท่าที่ดูหลายๆอันแล้ว ผมชอบวิธีนี้มากสุด
โดยการ replace PageResponseRenderer ด้วยการใช้กลไก decorator
    public static PageResponseRenderer decoratePageResponseRenderer(
@InjectService("PageMarkupRenderer")
final PageMarkupRenderer markupRenderer,
@InjectService("MarkupWriterFactory")
final MarkupWriterFactory markupWriterFactory, final Object delegate)
{

return new PageResponseRenderer()
{
public void renderPageResponse(Page page, Response response) throws IOException
{
MarkupWriter writer = markupWriterFactory.newMarkupWriter();
markupRenderer.renderPageMarkup(page, writer);
PrintWriter pw = response.getPrintWriter("text/html; charset=UTF-8");
writer.toMarkup(pw);
pw.flush();
}
};
}
}


2. ประเด็นของ form input element ที่ submit เป็นภาษาไทย เข้ามา
ตรงนี้ เราต้องแก้ไขด้วยการใส่ filter เข้าไป set ค่า encoding ให้กับ servlet request ก่อน
โดยปกติเราจะใช้ servlet filter ช่วย set ให้ก็ได้
แต่วิธีที่ลงให้ดูนี้ จะใช้กลไกของ IOC container ของ tapestry มาจัดการให้แทน
    public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration,
@InjectService("TimingFilter")
RequestFilter filter,
@InjectService("Utf8Filter")
RequestFilter utf8Filter)
{
// Each contribution to an ordered configuration has a name, When necessary, you may
// set constraints to precisely control the invocation order of the contributed filter
// within the pipeline.

configuration.add("Timing", filter);
configuration.add("encoding", utf8Filter);
}

public RequestFilter buildUtf8Filter(
@InjectService("RequestGlobals")
final RequestGlobals requestGlobals ) {
return new RequestFilter() {
public boolean service( Request request, Response response, RequestHandler handler ) throws IOException {
requestGlobals.getHTTPServletRequest().setCharacterEncoding( "UTF-8" );
return handler.service( request, response );
}
};
}


3. ประเด็นกรณี uri ที่ submit เข้ามา, มีภาษาไทย encode เข้ามาด้วย
กรณีนี้ รู้สึกจะเป็นกับ Tomcat เจ้าเดียว
ให้ configure file server.xml เพิ่ม
URIEncoding="UTF-8"

Related link from Roti

Wednesday, June 06, 2007

Spring Configuration

โดยปกติ คนที่ใช้ spring มักจะบ่นเรื่อง Configuration file ที่เต็มไปด้วย xml
ถึงกับมี solution จำนวนหนึ่งออกมาทดแทน เช่นไปใช้ groovy หรือไปใช้ annotation

เนื่องจากผมก็มีความเบื่อหน่ายใน xml file เหมือนกัน
ดังนั้นตอนนี้ จึงเริ่มมองหาวิธีที่จะมาทดแทน
สำหรับวิธีการ config สำหรับ spring ที่ผมสนใจตอนนี้ ก็มี
  • Annotation-based configuration
    วิธีนี้จะ bundle มาใน spring 2.1
  • Java Configuration
    แยกเป็น project มาต่างหาก โดยอยู่ใน project JavaConfig

ประเด็นหลักๆที่ต่างกันของ 2 วิธี ก็คือ แนวคิดเรื่องการแยกข้อมูลการ configuration ออกจาก source code
ซึ่งวิธีที่สอง จะดูดีกว่าในแง่นี้ เนื่องจากไม่มี code เกี่ยวกับ configuration ปนอยู่ใน bean เลย

ผมทดลองใช้โจทย์ ใน post ที่เคยเขียนเรื่อง Tapestry 5 IoC
มาลอง config ทั้ง 2 วิธีดู
เริ่มด้วยวิธีแรกก่อน
@Component("indexService")
public class IndexServiceImpl implements IndexService {

public void index(Document document, Long key) {
...
}

}

@Repository
public class RepositoryServiceImpl implements RepositoryService {

public Long persistent(Document document) {
...
return id;
}

}

@Component("uploadService")
public class UploadServiceImpl implements UploadService {

private IndexService indexService;
private RepositoryService repositoryService;
private Map<String, Parser> parserMap = new HashMap<String, Parser>();

public void upload(InputStream input, String type) {
Parser parser = parserMap.get(type);
Document document = parser.parse(input);
Long key = repositoryService.persistent(document);
indexService.index(document, key);
}

@Autowired
public void setIndexService(IndexService indexService) {
this.indexService = indexService;
}

@Autowired
public void setRepositoryService(RepositoryService repositoryService) {
this.repositoryService = repositoryService;
}

@Resource(name="parserMap")
public void setParserMap(Map<String, Parser> parserMap) {
this.parserMap = parserMap;
}
}

จะเห็นมี annotaion อยู่ดังนี้
  • @Component -> เข้าข่ายพวกที่เราเคยประกาศ bean id=".." ใน xml file
  • @Repository -> ต่างกับ Component ตรงที่จะใช้กับพวกที่เป็น Data access layer
    ,เข้าใจว่าตั้งชื่อให้ต่างกัน component เพื่อที่จะได้นำไปใช้อย่างอื่นได้ด้วยเช่น
    เอาไป implement พวก automatic translation of exception
  • @Autowired -> เป็นการ inject โดยใช้ type
  • @Resource -> ใช้ annotation ของ JSR-250 มา inject bean พวกที่ declare ไว้ใน xml file แบบเดิม
    หรือแบบที่ซับซ้อนกว่านั้นเช่น lookup จาก jndi

การใช้วิธีแรกนี้ ยังมีปัญหาไม่สามารถ config พวก map property ผ่านทาง annotation ได้
จึงต้องใช้ xml เข้ามาผสมด้วย
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.1.xsd"
>

<context:component-scan base-package="anno"/>

<bean id="parserMap" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="pdf">
<bean class="anno.impl.PdfParser"/>
</entry>
</map>
</constructor-arg>
</bean>

</beans>

จากข้างบน จะเห็นว่า เราใช้ component-scan เข้ามากำหนดว่า
เพื่อที่จะได้ข้อมูล bean ที่ทำ annotation ไว้, spring ต้อง scan กลุ่ม package ใดบ้าง

เห็นแล้วก็ยังไม่ชอบใจนัก
ลองดูวิธีที่ 2 บ้าง
ในวิธี Java Configuration นี้ เราจะ implement class ขึ้นมาแทน xml file
โดยการกำหนด @Configuration ไว้ที่ class,
ซึ่ง spring ก็จะใช้ class ที่มี annotation นี้ในการ config beans
@Configuration
public class MyConfiguration {

@Bean
public IndexService indexService() {
return new IndexServiceImpl();
}

@Bean
public RepositoryService repositoryService() {
return new RepositoryServiceImpl();
}

@Bean
private Map<String, Parser> parserMap() {
HashMap<String, Parser> map = new HashMap<String, Parser>();
map.put("pdf", new PdfParser());
return map;
}

@Bean
public UploadService uploadService() {
UploadServiceImpl impl = new UploadServiceImpl();
impl.setIndexService(indexService());
impl.setRepositoryService(repositoryService());
impl.setParserMap(parserMap());
return impl;
}

}

Note: พวก bean RepositoryServiceImpl, IndexServiceImpl, ...
เขียนแบบ POJO แท้ๆ ไม่ต้องมี Annotation ใดๆ ใส่ไว้ให้รบกวนลูกตาเลย

Note: ให้สังเกตุว่า parserMap เป็น private method ซึ่งเท่ากับว่า bean
นี้ไม่สามารถ access จาก method context.getBean('parserMap') ได้

เราสามารถใช้วิธี Java Configuration ผสมกับ xml ได้เช่นกัน
อย่างสมมติว่า เราต้องการให้ parserMap ถูก config จากข้างนอก
ก็สามารถ เขียนส่วน Configuration ใหม่ดังนี้
        @ExternalBean
public Map<String, Parser> parserMap() {
return null;
}

และไปกำหนด xml ข้างนอกแบบนี้
<beans>

<bean id="parserMap" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="pdf">
<bean class="anno.impl.PdfParser"/>
</entry>
</map>
</constructor-arg>
</bean>

<bean class="anno.configuration.MyConfiguration"/>

<bean class="org.springframework.config.java.process.ConfigurationPostProcessor"/>

</beans>


ถึงตอนนี้แล้ว ผมออกจะชอบวิธีที่ 2 มากกว่า

Related link from Roti

Tuesday, June 05, 2007

Unit Testing in Tapestry 5

testing ใน tapestry 5 แบ่งออกเป็น 2 พวกคือ
1. พวก unit testing, สามารถใช้ได้ทั้ง testng หรือ junit
class ต่างๆที่ช่วยในการ test อยู่ใน package tapestry-core
2. พวก integrated testing, อันนี้เป็นการ test ผ่าน browser โดยใช้ selenium เข้ามาช่วย,
class ที่ช่วยในการ test อยู่ใน package tapestry-test

class ที่เป็นพระเอกในเรื่อง unit testing ก็คือ PageTester
ลองดูตัวอย่างวิธีใช้ง่ายๆ

สมมติเรามี start page หน้าตาแบบนี้
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>Start Page</title>
</head>
<body>
<p id="greeting">${greeting}</p>
</body>
</html>

แล้วก็มี page class หน้าตาแบบนี้
public class Start
{
@Inject
private HelloService helloService;

public String getGreeting() {
return helloService.getGreeting();
}
}

ตัว Test case ก็จะเขียนอย่างนี้
public class TestStartPage extends Assert {
@Test
public void testPage() {
String appPackage = "demo";
String appName = "Test";
PageTester tester = new PageTester(appPackage, appName, "src/main/webapp");

Document document = tester.renderPage("Start");
assertEquals("hi", document.getElementById("greeting").getChildText());
}
}

จาก code ที่เราเห็นใน class "Start" เราจะเห็นว่า
มันมีการ inject service ที่ชื่อ "HelloService" เข้ามา
ปัญหาก็คือ ในตอน run จริง, service นี้จะได้มาจาก springframework
แต่ในขั้น unit testing นี้เราไม่อยากลากเอา service ตัวจริงๆเข้ามา test ด้วย
ดังนั้น เราต้องทำการสร้าง stub ของ HelloService ขึ้นมาก่อน
public class HelloServiceStub implements HelloService {

public String getGreeting() {
return "hi";
}

}

การนำ stub ไปใช้ ก็ทำได้โดยผ่านการ configuration ด้วย Module class
package demo.services;

public class TestModule {

public static HelloService buildHelloService() {
return new HelloServiceStub();
}
}

โดยชื่อและ package ของ TestModule ต้องสัมพันธ์กับ parameter ที่เรา pass ให้ PageTester
String appPackage = "demo";        
// นำไป solve หา class โดยใช้ชื่อ class = appPackage + ".services." + appName + "Module"
String appName = "Test";
PageTester tester = new PageTester(appPackage, appName, "src/main/webapp");


ส่วนการนำไปใช้จริง เราสามารถเขียนให้ซับซ้อน โดย simulate การ click
หรือการ submit ได้ด้วย ยกตัวอย่าง
        @Test
public void testPage() {
String appPackage = "demo.bid";
String appName = "Test";
PageTester tester = new PageTester(appPackage, appName, "src/main/webapp");

Document document = tester.renderPage("Start");

// ทดสอบ click action link
document = tester.clickLink(document.getElementById("plus"));
// assert ว่าค่าที่ render กลับมา เปลี่ยนไปอย่างที่ต้องการไหม
Node node = document.getElementById("c1").getChildren().get(1);
node = node.getChildren().get(5);
assertEquals("1", node.getChildText());
}

Related link from Roti

Saturday, June 02, 2007

ต้นไม้

ภาพถ่ายของ Jocke Berglund



links

Related link from Roti

Wednesday, May 30, 2007

จ่าย,ไม่จ่าย - ซื้อ, ไม่ซื้อ

Washington Post มีบทความน่าสนใจเรื่อง
Mental Accounting

เขาเกริ่นนำว่า
สมมติว่าเราไปซื้อตั๋วดูหนัง 170 บาท
แล้วเกิดทำตั๋วหนังหายไป
ถามว่า เราจะซื้อตั๋วหนังใบใหม่ไหม

กับอีกสถานการณ์หนึ่ง
สมมติว่าเราไปซื้อตั๋วหนังเหมือนกัน
แต่ก่อนจะซื้อ
เราเปิดกระเป๋าตังค์แล้วพบว่า
เงินเราหายไป 170 บาท
ถามว่า เราจะซื้อตั๋วดูหนังหรือไม่

ด้วยมูลค่าการสูญเสียที่เท่ากัน
(จากการทดลอง พบว่า)
46 % ของกลุ่มแรก ตกลงใจซื้อตั๋ว
88 % ของกลุ่มหลัง ตัดสินใจซื้อตั๋ว

Related link from Roti

Monday, May 28, 2007

Tapestry 5 Ioc

เมื่อรู้ว่า T5 เปลี่ยนวิธี config ไปแล้ว
ผมในฐานะที่มี learning style เป็นแบบที่ต้องทดลองทำด้วยตัวเองจึงจะเข้าใจ,
ก็เลยลองทดสอบ config tapestry5 IoC เปรียบเทียบกับ spring

เริ่มด้วยการสมมติสถานการณ์ก่อน
สมมติว่าเรามี service ที่ใช้ในการ upload content เข้า repository
public interface UploadService {

public void upload(InputStream input, String type);

}

การทำงานภายในของ service นี้ก็คือ
1. ทำการ parse content, ซึ่งวิธี parse ก็ขึ้นอยู่กับ type ของ content
2. delegate การจัดเก็บ content ให้กับ RepositoryService เป็นคนทำ
3. ทำ index ของ content นั้น โดย delegate ให้กับ IndexService เป็นคนจัดการ
public class UploadServiceImpl implements UploadService {

private IndexService indexService;
private RepositoryService repositoryService;
private Map<String, Parser> parserMap = new HashMap<String, Parser>();

public void upload(InputStream input, String type) {
Parser parser = parserMap.get(type);
Document document = parser.parse(input);
Long key = repositoryService.persistent(document);
indexService.index(document, key);
}

public void setIndexService(IndexService indexService) {
this.indexService = indexService;
}

public void setRepositoryService(RepositoryService repositoryService) {
this.repositoryService = repositoryService;
}

public void setParserMap(Map<String, Parser> parserMap) {
this.parserMap = parserMap;
}
}

ถ้าร้อย service ด้วย spring เราจะต้องเขียนดังนี้
<beans>
<bean id="indexService" class="test.service.impl.IndexServiceImpl"/>

<bean id="repositoryService" class="test.service.impl.RepositoryServiceImpl"/>

<bean id="uploadService" class="test.service.impl.UploadServiceImpl">
<property name="indexService" ref="indexService"/>
<property name="repositoryService" ref="repositoryService"/>
<property name="parserMap">
<map>
<entry>
<key><value>pdf</value></key>
<bean class="test.service.impl.PdfParser"/>
</entry>
<entry>
<key><value>word</value></key>
<bean class="test.service.impl.MsWordParser"/>
</entry>
</map>
</property>
</bean>
</beans>


พอมาเป็น Tapestry5 IoC
สิ่งที่เปลี่ยนไปอย่างมาก ก็คือ "ไม่มี xml อีกแล้ว"
เมื่อไม่มี xml, เราก็ต้อง define module descriptor เป็น java แทน
public class MyAppModule {

/**
* bind ใช้กับกรณีพวก simple bean, ไม่ต้องมีอะไรให้ set มากมาย
*/

public static void bind(ServiceBinder binder) {
binder.bind(RepositoryService.class, RepositoryServiceImpl.class);
binder.bind(IndexService.class, IndexServiceImpl.class);
}

/**
* กรณีพวก bean ที่ซับซ้อนมากขึ้น ก็ให้ตั้งชื่อ method ว่า build นำหน้า
* แล้วก็กำหนด parameter ตาม dependency ที่ต้องการ
* โดยลำดับจะเป็นอะไรก่อนหลังก็ได้
*/

public static UploadService buildUploadService(
IndexService indexer,
RepositoryService repositoryService,
Map<String, Parser> configuration) {

UploadServiceImpl impl = new UploadServiceImpl();
impl.setIndexService(indexer);
impl.setRepositoryService(repositoryService);
impl.setParserMap(configuration);
return impl;
}

/**
* ตรงนี้แหล่ะที่ tapestry5 IoC ยังเหนือกว่า Spring
* เราสามารถกำหนด contribute ที่ทำหน้าที่ provide configuration ให้กับ builder ข้างบน
* โดย contribute method สามารถ plugin เข้ามาจาก module อื่นๆได้
* ทำให้มันมีลักษณะเป็น modular มากกว่า spring
*/

public static void contributeUploadService(MappedConfiguration<String, Parser> configuration) {
configuration.add("pdf", new PdfParser());
configuration.add("word", new MsWordParser());
}
}

เวลา run ก็
        public void run() {
RegistryBuilder builder = new RegistryBuilder();
builder.add(MyAppModule.class, ParserModule.class);

Registry registry = builder.build();

UploadService service = registry.getService(UploadService.class);
service.upload(null, "pdf");
}

ขยายความเพิ่มเติมเรื่อง contribution ของ Tapestry5 อีกนิดหนึ่ง
contribution ใน T5 (จริงๆมันมีตั้งแต่ version 4 แล้ว)
มันมีลักษณะเป็น aggregate นั่นคือ มันจะ scan หา contribution ทั้งหมดที่มี
ไม่ว่าจะอยู่ใน jar file หรือใน classpath
จากนั้น จึง aggregate และส่งต่อให้ builder method
,feature ที่ไกล้เคียงสุดของ spring สำหรับกรณีนี้ ก็คือ collection merging (spring version 2)

Note: ทั้ง spring และ tapestry5 IoC ต่างมี feature พวก auto binding ทั้งคุ่
แต่ผมไม่นิยมใช้ เนื่องจากการประกาศว่าต้องใช้อะไรบ้าง มันชัดเจนกว่าในตอนที่ต้องไล่ code เพื่อแก้ไขโปรแกรม

Related link from Roti

Thursday, May 24, 2007

tapestry 5

ช่วงนี้ลูกหลับกลางวันเป็นเวลา เลยได้มีโอกาสลองเล่น tapestry 5
หลังจาก download tutorial มาทดสอบดูแล้ว
ก็พบ issue ที่น่าสนใจดังนี้
1. เดิม tapestry หรือ framework อื่นๆมักจะใช้ servlet
เป็นจุดตั้งต้น framework ของตัวเอง
แต่ tapestry 5 เปลี่ยนไปใช้ filter เป็นจุดเริ่มต้น
<filter>
<filter-name>app</filter-name>
<filter-class>org.apache.tapestry.TapestryFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>app</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

น่าสนใจมาก ว่าการเปลี่ยนแบบนี้จะนำมาซึ่งข้อดีและข้อเสียอะไรบ้าง

2. การเปลี่ยน page เดิมจะใช้วิธี forward เป็นหลัก
แต่ใน tapestry5 จะใช้ redirect เป็นหลักแทน
ซึ่งข้อดี ก็คือการ handle กรณี user กด refresh จะทำได้ถูกต้องขึ้น

3. การเริ่มต้นสร้างโปรเจคใหม่ทำได้ง่ายมาก (อิทธิพลจาก rails)
ด้วยการ integrate กับ maven, ทำให้เรา create new project,
running project(start server) ได้ภายใน 2 command line.

4. loop การแก้ไข + ทดสอบ + แก้ไข + ทดสอบ ...
โดยไม่ต้อง restart server ทำได้เรียบร้อยดี
ยกเว้น case ที่เราเพิ่ม page ใหม่ๆ เข้าไปหลังจาก start server ไปแล้ว
ที่ต้องมีการ restart server ใหม่

5. ระยะเวลาการ start server เร็วกว่าเดิมแบบไม่เห็นฝุ่น
เดิมประมาณ 3-7 วินาที ของใหม่ หลัก 300 millisec
ที่เป็นเช่นนี้เพราะมีการ rewrite Ioc container ใหม่

6. เปลี่ยน Ioc(Inversion of Control) ไปใช้ java code + annotaion
แทน xml file, อันนี้แหล่ะตัวงงเลย เพราะผมใช้ xml ใน spring มาจนเคยตัวแล้ว

Related link from Roti

Wednesday, May 23, 2007

semaphore

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

ลองเปิด dictionary ดู ก็พบว่า
semaphore มีรากศัทพ์มาจากสองคำคือ
sema -> sign
phoros -> carrying
รวมกันแล้ว ก็คือ system ที่ใช้ส่งข้อมูลระหว่างกัน โดยอาศัยการมองเห็นเป็นสำคัญ

ใน wikipedia เขาเล่าว่า ฝรั่งเศษ เป็นคนเริ่มใช้ก่อน
โดยเริ่มนำมาใช้อย่างจริงจังในช่วงปี 1790-1795
ซึ่งเป็นช่วงที่เกิดการปฎิวัติ (French Revolution)
โดยเขาจะสร้างสถานี (staion) ใล่กันไป
แต่ละสถานีอยู่ห่างในระยะที่มองเห็นกันได้
เมื่อมี information ที่จะส่ง สถานีที่ส่งก็จะปรับ sign (แล้วแต่ว่าเลือกออกแบบ sign ไว้อย่างไร)
ของตัวเอง, สถานีถัดไปเมื่อมองเห็นก็จะปรับของตัวเองตาม
copy กันเป็นทอดๆไป จนกว่าจะถึงปลายทาง
(จะเห็นว่ามันเหมือนวิธีส่งโทรเลขเลย
ดังนั้นจึงมีชื่อเรียกอีกอย่างว่า optical telegraph)

ลองดูตัวอย่าง sign บ้าง
sign แบบแรกที่เราคุ้นเคย ก็คือสัญญาณธง


ส่วนพวก รถไฟ,
ในยุครถจักรไอน้ำ
เมื่อจำนวนรางที่เชื่อมต่อกันมีมากขึ้น
ปริมาณขบวนรถไฟที่วิ่งมีมากขึ้น
ก็เลยต้องหาวิธีการสื่อสารระหว่างคนขับรถไฟ กับนายสถานีขึ้นมา
เพื่อให้สามารถควบคุมการวิ่งของรถไฟให้มีประสิทธิภาพสูงสุด
รวมทั้งป้องกันไม่ให้เกิดอุบัติเหตุ
ก็เลยมีการ apply นำ semaphore มาใช้


Note: รูปเพิ่มเติมใน flickr

ดูแล้ว semaphore ที่ทาง computer นำมาใช้
จะมาจากแนวพวก semaphore ที่ใช้ในวงการรถไฟ
เนื่องจากเป็นเรื่องของการจัดสรรทรัพยากรที่มีอยู่จำกัด
(ทรัพยากรที่จำกัดของรถไฟ ก็คือราง)
ซึ่งเป็นปัญหาพวก concurrency แบบเดียวกัน

Related link from Roti

Monday, May 21, 2007

unit_of_work

ใน rails เวลาเราเขียน action ส่วนที่เกี่ยวกับการ save, update
หน้าตาของ code ตรงนั้นส่วนใหญ่จะยึดตามแบบนี้
  def create
@role = Role.new(params[:role])
if @role.save
flash[:notice] = 'Role was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end

ซึ่งดูแล้วก็รู้เรื่องดี, แต่สำหรับผมเวลาเขียน code ตรงส่วนนี้ทีไรมันรู้สึกขัดๆ

Obie Fernandez ก็คงรู้สึกแบบเดียวกัน
แต่เขาหาวิธีแก้ไขได้เนียนดี
โดยเขาเปลี่ยนวิธี save เป็นแบบนี้แทน
  def create
case unit_of_work do
@role = Role.new(params[:role])
end

when Success
redirect_to :action => 'list'

when ValidationError
render :action => 'new'
end
end

เป็นการเอา block + case statement มา combine กันได้สวยดี
โดยตัว unit_of_work เป็น method ที่รับ block เข้าไป

Related link from Roti

Wednesday, May 16, 2007

งัวเงีย

เมื่อคืนขณะกำลังหลับลึก ก็ได้ยินเสียงหวานดังแว่วๆมา "พลวัฒน์คะ"
ก่อนที่จะเริ่มเคลิ้มไปตามเสียงหวานๆ ก็ได้ยินเสียงที่ตามมาว่า "ชงนมให้ลูกหน่อยค่ะ"
เท่านั้นแหล่ะ, กล้ามเนื้อท้องก็ทำงาน ดีดตัวขึ้นมานั่งโดยอัตโนมัติ

ขั้นแรกสุดในการชงนม ก็คือต้องหาขวดนมให้ได้ก่อน
เนื่องจากอุปสรรคเรื่องความมืด + สายตายังไม่ปรับ focus + สมองที่ยังงัวเงียอยู่
algorithm ที่ดีที่สุดในการหาขวดและจุกนมในความมืด ก็คือ brutal force
หลังจากคุ้ยและคลำกองขวดนมอยู่นานมาก (นานในความรู้สึก เพราะมันอยากกลับนอนเต็มที่แล้ว) ก็ได้ขวดนมขนาดที่เหมาะสำหรับเด็กอ่อนออกมา
ชั้นที่สอง ก็คือ ตวงน้ำให้ได้ 2 Oz. อันนี้ยากขึ้นอีก
เพราะว่า ต้องกัดฟันบังคับให้สมองส่วนที่เกี่ยวกับ "มิติ" มันยอมทำงาน
เมื่อได้ขวดนมพร้อมน้ำแล้ว ก็จัดการตักนมผงขึ้นมาหนึ่งช้อน
(ไม่ยากแล้ว เพราะ function "มิติ" มันทำงานแล้ว)
ผสมเข้าด้วยกันแล้วก็เขย่าๆ
ส่งต่อให้ภรรยา จากนั้นก็กระโดดกลับขึ้นเตียง
(หลังจากที่เขียนเรื่องนี้แล้ว คำถามที่ตามมาอีก ก็คือ
เอ๊ะ แล้วทำไม แฟนเราถึงไม่ชงนมเองหว่า)

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

ปล. เรื่องนี้สามารถเปลี่ยนบริบท จากชงนม
ไปเป็น software development ได้โดยไม่เสียความหมาย

Related link from Roti

Monday, May 14, 2007

น้องใหม่มาแล้ว

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

ลาคลอดครั้งนี้ (ฝ่ายชายก็ลาคลอดได้ คราวก่อนลาไปเก้าเดือน, คราวนี้คงลาแค่ 3 เดือน)
ผมรับหน้าที่หลักคือดูแลคนโต
จับกินข้าว,อาบน้ำ, ล้างก้น, เล่นทุกรูปแบบ, เล่านิทาน, พาไปเที่ยว
ส่วนลูกคนเล็ก ผมรับหน้าที่ อาบน้ำ กับ หมั่นตื่นมาดูในช่วงตีสองเป็นต้นไป

Update; เมื่อคืนลูกคนเล็กมันรับน้องพ่ออีกแล้ว
ต้องอุ้มเดินหกทุ่มถึงตีหนึ่ง
แล้วก็ตีห้าถึงแปดโมงเช้า
ส่วนตีสอง เป็นเวรของลูกคนโต ตื่นขึ้นมาอิจฉาน้อง
ต้องนอนปลอบกันเกือบครึ่งชั่วโมง

Related link from Roti