Tag Archives: Java

ยิ่งแก่ยิ่งเขียนโค้ดทำงานช้าลง

ช่วงนี้ที่กลุ่ม Thai Functional Programming Enthusiasts​ ใน facebook มีกิจกรรมทำโจทย์รายวันกันอยู่ เมื่อวานนี้พี่ป้อเอาโจทย์ GCJ2009 มาให้ทำ เราก็คิดว่าถ้าเป็นโจทย์สมัยนั้นต้องเคยทำสิ ก็ไปเปิดดูพบว่ามีจริงๆด้วย

สมัยนั้นเขียนโค้ดอ่านยากมาก แต่น่าจะเป็นเพราะการแข่งขันมันไม่ได้เน้น code quality ด้วย วันนี้ลองทำเวอร์ชั่น clojure ออกมา (ส่วนตัวรู้สึกว่า)มันอ่านง่ายกว่ามาก แต่รัน large input ไม่ทัน 8 นาที ส่วนโค้ดเก่า ข้อมูลใน web gcj บอกว่ารัน large ทัน แหมะผ่านมา 6 ปีพัฒนาการย้อนหลังนะครับเนี้ย

โค้ดทั้งสองเวอร์ชั่นอยู่ที่นี่ครับ https://gist.github.com/visibletrap/f669448f5a833aee146e

ยังไม่รู้เหมือนกันว่าโค้ด clojure ช้าตรงไหน regex?, eval? หรือช้าโดยปกติ ไว้ต้องไปลอง inspect ดู

จาก argument สู่พลัง

ช่วงนี้กำลังอ่านหนังสือเล่มนี้อยู่ครับ Practical Object-Oriented Design in Ruby มันเป็นหนังสือที่ในช่วงครึ่งปีหลังของปี 2012 นี้ใครๆ ใน Ruby community ก็พูดถึงและแนะนำกันทุกคน เห็น user rating จากค่ายหนังสือต่างๆ ก็ไม่ต่ำกว่า 4.5 ดาว ทั้งนั้น

หนังสือเล่มนี้กล่าวถึงการดีไซน์ Object-Oriented แบบลงลึกละเอียดลึกถึงระดับตัวแปรเลยทีเดียว ตอนนี้ผมอ่านถึงบทที่ 3 รู้สึกว่าหนังสือเล่มนี้ให้ความรู้สึกเดียวกับตอนอ่าน Clean Code เลย แต่เน้นลงมาที่ตัว OO มากกว่า และผูกกับ syntax ของ Ruby นิดหน่อย มีบางจุดที่อาจจะใช้ไม่ได้ถ้าเขียนด้วยภาษาอื่น

ในหนังสือเล่มนี้มีกล่าวถึง dependencies ของ object 4 อย่าง อันได้แก่

  1. Class name
  2. Message name (method name)
  3. Arguments ของ message นั้น
  4. ลำดับของ Arguments เหล่านั้น

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

3 ข้อแรกจาก 4 ข้อนั้น ผมเห็นด้วยทันทีตอนที่อ่าน แต่สำหรับข้อ 4 ผมมีข้อที่คลางแคลงใจนิดหน่อยจะมาเล่าให้ฟัง ลองดู code นี้ก่อนครับ

class A
  def a(b, c, d)
    @b = b
    @c = c
    @d = d
  end
  ...
end

ผู้เขียนกล่าวว่าถ้า class A นี้ถูกเรียกใช้จากโปรแกรมเมอร์คนอื่น ปัญหาหนึ่งที่เราจะพบคือ เราจะไม่สามารถแก้ลำดับของตัวแปร หรือเพิ่มลดตัวแปรโดยไม่ให้กระทบผู้เรียกใช้ได้ เป็นผลให้การแก้ไข code ชุดนี้เพื่อไป reuse ในที่อื่นๆ ทำได้ยากขึ้น วิธีการที่ผู้เขียนแนะนำคือให้รับ arguments เป็น Hash แทน ดังนี้

class A
  def a(args)
    @b = args[:b]
    @c = args[:c]
    @d = args[:d]
  end
  ...
end

เมื่อทำเช่นนี้แล้ว เราจะมีอิสระมากขึ้นได้การเพิ่มลด argument ไม่ให้กระทบผู้ที่เรียกใช้งานอยู่ แต่มันกลับเป็นวิธีการที่ผมไม่เห็นด้วยในช่วงเวลาแรกที่อ่าน แต่พอได้ลองคิดดูผมก็เข้าใจอะไรบางอย่างมากขึ้น

สาเหตุที่ผมไม่เห็นด้วยกับการส่ง hash เป็น arguments เพราะผมเคยมีประสบการณ์กับ code Java ประมาณนี้

public class A {
  private B b;
  private Map options;
  public A(int x, int y, Map options) {
    b = new B(options);
    this.options = options;
    ...
  }
  public a() {
    int i = b.m();
    int j = i + options.get("something");
    ...
  }
}

public class B {
  private C c;
  private Map options;
  public B(Map options) {
    c = new C(options);
    this.options = options;
  }
  public int b() {
    options.get("anotherThing");
    ...
  }
}

การที่ options ถูก pass ต่อไปเรื่อยๆสู่ class ที่เรียกใช้ และแต่ละ class รายทางก็ดึงค่าบางอย่างออกมาใช้ มันสร้างปัญหาทำให้เราไล่ code ยากว่าจริงๆ หากเราต้องการเรียกใช้ A เนี้ย เราต้อง pass key-value อะไรให้ options บ้าง เพราะเราต้องไปไล่ code ลงไปตาม class ที่ A ส่ง options ไปให้ทั้งหมดลงไปเรื่อยๆ นี่เป็นสาเหตุที่ทำให้ผมไม่ชอบการ pass Map(Hash) เป็น argument ตั้งแต่นั้นเป็นต้นมา ผมคิดว่าผมขอเขียนแบบประกาศตัวแปรให้รู้ๆ กันไปเลยดีกว่า A ต้องการอะไร

แต่คุณเห็นความแตกต่างระหว่างการใช้ args ของ code ชุดที่ 2 และ options จากชุดที่ 3 มั้ยครับ ใน code ชุดที่ 2 ที่ผมแปลงมาจากในหนังสือนั้น args ไม่ได้ถูกส่งต่อ ค่าทั้งหมดที่มากับ hash นั้นจะถูกดึงออกมาเก็บไว้ใน instance variable ทั้งหมด แล้ว hash นั้นก็จะถูกทิ้งไป ปัญหาที่ทำให้ผมไม่ชอบการ pass hash เป็น argument ก็จะไม่เกิด จุดนี้ทำให้ผมเข้าใจและเห็นด้วยกับแนวทางที่ผู้เขียนเสนอขึ้นมา ผู้เขียนยังเสนอการต่อยอดจาก code ชุดที่ 1 และ 2 อีกดังนี้ครับ

class A
  def a(b, c, d)
    @b = b
    @c = c
    @d = d
  end
  ...
end

class FactoryA
  def self.createA(args)
    A.new(args[:b], args[:c], args[:d])
  end
end

จาก code นี้เราจะเปิด FactoryA เป็น external api รับ args เป็น hash จากนั้นส่งต่อให้ implementation ภายในคือ class A อีกที การเขียน code แบบนี้ทำให้ public api เราคงที่ ในขณะที่เราสามารถแก้ไข เปลี่ยนแปลง refactor internal implementation ของเรา (A) ได้เต็มที่โดยไม่ต้องกังวลว่าจะกระทบกับคนที่เรียกใช้มัน

การจะเลือกใช้แนวทางใดผมว่าเป็นการชั่งน้ำหนักระหว่าง การบังคับ และ วินัย ครับ ถ้าเราอยู่ในที่ๆ คนไม่ยอมมีระเบียบวินัย(ส่ง options ไปทั่ว)เราก็ต้องใช้การบังคับแทน(การรับ argument แบบตายตัว)เพื่อที่จะไม่เกิดความวุ่นวาย แต่ถ้าเราอยู่ในจุดที่คนมีระเบียบวินัย (ไม่ส่ง args ต่อๆไป)แล้ว เราสามารถสร้างสรรค์อะไรดีๆ ได้อีกมากครับ

แนวคิดในช่วงนี้ของผมค่อนข้างมุ่งไปในทางนี้ดังที่เคยเขียนไปไว้เรื่อง องค์กรเปิด

การที่เราจะสร้างสังคม องค์กร ที่เจ๋งๆได้นั้น ผมว่าเราควรให้พลังและอำนาจแก่คนในองค์กรอย่างเต็มที่ เพื่อให้เค้าได้ใช้ความคิดสร้างสรรค์ผลิตสิ่งดีๆ ออกมา แต่ทั้งนี้ทั้งนั้นมันก็มีข้อจำกัดเช่นกันว่า คนในสังคม องค์กรนั้นๆ จะมีความรับผิดชอบและระเบียบวินัยเพียงใด ถ้าคนในสังคมและองค์กรของคุณมีศักยภาพ ผมอยากให้ๆอิสระแก่พวกเค้าครับ แล้วคุณอาจจะได้อะไรดีๆกลับมาอย่างคาดไม่ถึง

Update: ทั้งหลายทั้งปวงจะบอกว่านี่ไม่ได้หมายความว่าเราควรส่ง argument เป็น hash เสมอไปนะครับ เลือกใช้ให้เหมาะสมกับเวลาและโอกาสครับ อย่างกรณี hash นี้เหมาะกับการเขียน external api ให้คนอื่นมาเรียกใช้ครับ ขอขอบคุณพี่แน็ทที่ทักท้วง