JavaScript ตัด string แบบเนียนๆ

ช่วงที่ผ่านมามีโจทย์นึงจากลูกค้าให้ช่วยหา logic ตัด string แบบที่เขาต้องการบน JavaScript ให้หน่อย โดยเจ้า JavaScript ของผมเนี่ยเป็น mvc api ที่จะส่งข้อมูลลงมาจะเป็น string ที่มี tag span html ปนอยู่ใน string ด้วย (back end เป็นตัวใส่ให้) เพื่อที่จะเป็นจุดอ้างอิงให้ลูกค้าเอาใช้กำหนดใน css ของเขาครับ

HTML SPAN ที่ส่งมาจะมี class (ชื่อสมมติ) ดังนี้ ‘Highlight’, ‘Foo’ และ ‘Bar’สำหรับ class ‘Highlight’ นี่จะผสมกับ class อื่นได้ แต่จะอยู่ลำดับหลังเสมอเช่น หรือ จะไม่มี class=’Highlight Bar’ เด็ดขาด ในแต่ละ string จะมี span tag โผล่มากี่ตัวก็ได้ ไม่จำกัด

สิ่งที่ลูกค้าต้องการ: ลูกค้าต้องการให้ตัด span ที่มี class ‘Foo’ และ ‘Bar’ รวมไปถึงทุกอย่างที่อยู่ใน span tag ของมันออกให้หมด แต่ span ที่มี class ‘Highlight’ ไม่ต้องยุ่งกับมัน เช่น

Input:

Time Warner Cable Reports Development of <span class='Bar Highlight'>AAA</span> First IP Set-Top Box, Sees Boxes <span class='Bar'>CATTT</span> in Select Markets by Year End  
<span class='Foo'>GE.N</span>  <span class='Foo'>TWC.N</span> <span class='Bar'>Hello</span>

Output:

Time Warner Cable Reports Development of <span class='Bar Highlight'>AAA</span> First IP Set-Top Box, Sees Boxes in Select Markets by Year End

Code แรกที่ผมทำนั้นเน้นให้ logic ถูก ทำงานได้ ยังไม่ได้คิดถึงเรื่อง Performance อะไรนัก เอาแบบวน loop recursive ถึกๆ แล้วหา string ด้วย indexOf แล้วตัดด้วย replace กันดื้อๆ เลย แบบนี้ครับ (gen มาจาก CoffeeScript นะ)

var arrayTag, filterTag, msgBase, removeTag, tagBar, tagFoo, tagSPAN;

tagBar = "<span class='Bar'>";
tagFoo = "<span class='Foo'>";
tagSPAN = "</span>";

arrayTag = [tagQuoteRef, tagStoryRef, tagNewsSearchID, tagNewsSearch];

msgBase = "Time Warner Cable Reports Development of <span class='Bar Highlight'>AAA</span> First IP Set-Top Box, Sees Boxes <span class='Foo'>CATTT</span> in Select <span class='Foo Highlight'>Maeooooo</span> Markets by Year End  <span class=Bar'>GE.N</span>  <span class='Bar'>TWC.N</span> <span class='Foo'>Hello</span> ";

filterTag = function(msgStr, filterStr, index) {
	var indexSpan, indexStart;
    	indexStart = -1;
	indexSpan = -1;
   	tmp = "";
    	indexStart = msgStr.indexOf(filterStr, index);
    	if (indexStart !== -1) {
      		indexSpan = msgStr.indexOf(tagSPAN, indexStart);
      		msgStr = msgStr.replace(msgStr.slice(indexStart, indexSpan + 7), '');
      		return filterTag(msgStr, filterStr, indexSpan + 8);
    	} else {
      		return msgStr;
	}
};

removeTag = function(msgStr) {
	var tagRemove, _i, _len;
    	console.log("Orignal Text: " + msgStr);
    	for (_i = 0, _len = arrayTag.length; _i < _len; _i++) {
      		tagRemove = arrayTag[_i];
      		msgStr = filterTag(msgStr, tagRemove, 0);
    	}
    	console.log("Remove Text: " + msgStr);
};

removeTag(msgBase);

รองรันๆ แล้วก็โอเค ไม่มีหลุดอะไร แต่ส่วนตัวคิดว่าถ้ามันเขียนด้วย Regular expression น่าจะเนียนกว่านี้ แต่ผมเขียนไม่เป็น เลยลองไปถาม @neizod ดู ซึ่งเนยซดก็ตอบว่า code ก็โอเคแหละ แต่ถ้าคิดถึง performance น่าจะลองแบบนี้นะครับ

msgBase =msgStr.split(/<span class='(?:Foo|Bar)'>.*?<\/span>/).join('')

เขร้ code แม่มสั้นสัดๆ ลองเทสด้วย เวบ jsperf แล้วเร็วโคตรๆ ผมก็เลยส่งอันนี้ให้ Dev ทีมผม review ดู น้องเขาก็เอาไป review สักพักแล้วก็บอกว่าอันนี้น่าจะเร็วกว่านะ

msgBase = msgStr.replace(/<span class='(Foo|Bar)'>.*?<\/span>/g,'');

ซึ่งผลก็ออกมา เร็วขึ้นเยอะ เลย \ w / ซึ่งผมก็เอาอันนี้แหละไปบอกลูกค้าอีกที

สรุป:

  • ความรู้และความเข้าใจเรื่อง Regular expression ถือว่าสำคัญมาก ซึ่งผมดันไม่รู้เลย
  • การได้เห็น code ดีๆ มันเปิดหูเปิดตาเราได้เยอะเลย

Code สวย

เมื่ออาทิตย์ก่อนหน้าโน๊นพี่ roof ตั้งคำถามใน fb ว่า ตั้งแต่ทำงานมามีที่ทำงานที่ไหนให้ความสำคัญกับคำว่า “โค้ดสวย” บ้างครับ แม้ผมจะตอบกวนตีนไปว่าคนเขียน code สวยสำคัญกว่า แต่ตัวคำถามก็น่าสนใจจนต้องมาย้อนนึกคำตอบอยู่เหมือนกัน

สมัยผมยังทำงานเป็นโปรแแกรมเมอร์บริษัทแรกที่ผมทำ (อย่างน้อยก็ทีมที่ผมอยู่) ไม่มีแนวคิดเรื่อง code สวยครับ คือมีสอน coding convention บ้างว่าตัวแปรควรจะตั้งชื่ออย่างไร ชื่อ database แต่ละ table ควรจะตั้งอย่างไรก่อนเข้าทำ project แค่นั้น

แต่ตอนได้ทำงานจริงๆ ผมและเพื่อนร่วม project ก็ได้เรียนรู้ว่า code แบบไหนที่คนเขียนแล้วด่า (ด่าแรงๆ ก็ด่ายันพ่อคนเขียน code อีกที) แล้วพวกเราก็เรียนรู้ที่จะไม่เขียน code แบบนั้น ซึ่งส่วนใหญ่ไอ้ที่เราด่าจะเป็นอะไรที่ง่าวสุดๆ แบบเห็นได้ชัด ส่วนไอ้ที่ไม่ควรเขียนแต่เราไม่รู้เนี่ยเราก็นั่งอ่านผ่านๆ ตาทุกวันจนกระทั่งเกิดความเคยชิน ติดนิสัยเขียนแบบนั้นตามไปด้วย ซึ่งเหตุการณ์นี้ก็ยังเกิดตอนผมมาทำบริษัทที่ 2 อยู่ดี

ในบริษัทปัจจุบันที่ผมทำอยู่ ผมไม่ได้เป็นโปรแกรมเมอร์แล้ว (ที่นี่เรียกว่า dev) ผมไม่รู้ว่ามีการเน้นการเขียน code ที่ถูกต้องหรือไม่ แต่ผมก็รู้ว่าพวกเขามีการทำ code review กันทุกอาทิตย์ ส่วนงานของผมจะเน้นไปที่อ่านและ debug code ที่ตัวเองไม่ได้เขียนเป็นหลัก code ที่ผมมีความสุขที่ได้ทำงานด้วยคือ code ที่ผมเห็นแล้วรู้ทันทีว่ามันทำอะไรโดยไม่ต้องอ่าน comment หรือ debug ดู และถ้า code นั้นผมเห็นแล้วถึงกับร้องว่าเหยด คิดได้ยังไงจะยิ่งฟินสุดๆ ซึ่งก็พอจะมี dev ที่เป็นเพื่อนผมอยู่ 2-3 คนที่เขียนได้ประมาณนี้แล้วก็มีหลายๆ ครั้งที่ส่วนนั้นมันเจ๋งจนผมจำได้ว่าเวลาจะไล่อะไรก็ไปเริ่มไล่ในจุดนั้นแหละ แต่ไอ้ code พวกนั้นก็ไม่ได้รับรองว่าตัวมันทำงานจริงๆ มันจะช้าจะเร็วอย่างไร หรือนานๆ ไปมันจะมี bug ไหม ควร refactor อีกรึเปล่า (เพราะไม่ใช่หน้าที่ผม – แต่ก็เห็น dev refactor กันอยู่บ้างนานๆ ทีอ่ะนะ)

ทั้งหมดทั้งปวงที่กล่าวมาก็คือผมเองก็ไม่รู้ว่า code ที่สวยนั้นเป็นอย่างไร ส่วนตัวชอบ code ที่อ่านง่าย เข้าใจได้เลยโดยไม่ต้องอ่าน comment หรือ debug และ code นั้นจะไม่ควรจะง่าวระดับสุดๆ จนดูแล้วอยากด่านั่นเอง

ป.ล. พี่ roof เขียนถึงการพัฒนา software ที่เน้นเรื่อง performance จนละเลย maintainability ไว้แล้วครับ

วิธี set PATH Solaris เพื่อ configure กับ make file

ช่วงนี้มีเรื่องให้ต้อง compile โปรแกรมบน Solaris 10 x86 แล้วก็ติดปัญหาเรื่อง set compiler path หลายครั้งจนขอจดไว้ต้อง set อะไรบ้าง

พอโหลด source มา untar file เสร็จสั่ง configure ก็ขึ้นแบบนี้มาเลย

# ./configure
checking build system type... i386-pc-solaris2.10
checking host system type... i386-pc-solaris2.10
checking target system type... i386-pc-solaris2.10
checking for gcc... no
checking for cc... no
checking for cl.exe... no
configure: error: in `/disk2/opt/ruby':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details
#

ไปค้นๆ ดู ต้องไปหา gcc ครับ ซึ่ง default ของ Solaris ในกรณีที่ไม่ได้ลง Sun Studio มันอยู่ที่ /usr/sfw/bin เราก็ export PATH ไป

# export PATH=/usr/sfw/bin:$PATH

พอสั่ง ./configure ก็จะเจอ error ว่าไม่มี make -*-

checking for prefix of external symbols... ./configure: line 18702: conftest.o: command not found
NONE
checking pthread.h usability... yes
checking pthread.h presence... yes
checking for pthread.h... yes
checking if make is GNU make... ./configure: line 18909: make: command not found
no
checking for safe null command for make... configure: error: no candidate for safe null command

ไปค้นๆ ดู make ไม่ได้อยู่ใน /usr/sfw/bin แต่อยู่ใน /usr/ccs/bin ต่างหาก -*-

ดังนั้นวิธีการ export ที่ถูกต้องเพื่อที่จะ configure และ make ได้คือ

# export PATH=/usr/ccs/bin:/usr/sfw/bin:$PATH

ที่นี่ก็จะ build ได้ พอ make install โปรแกรมก็จะไปอยู่ใน /usr/local แล้วเราก็ค่อยไปสร้าง softlink อีกทีครับ