ฉันจะใช้จาวาสคริปต์ได้อย่างไร


9

ฉันเชื่อว่าฉันได้เรียนรู้แนวคิดพื้นฐานบางส่วน / มากที่สุด / ส่วนใหญ่ที่เกี่ยวกับการเขียนโปรแกรมการทำงานใน JavaScript อย่างไรก็ตามฉันมีปัญหาในการอ่านรหัสฟังก์ชั่นโดยเฉพาะแม้แต่รหัสที่ฉันเขียนและสงสัยว่าใครสามารถให้คำแนะนำเคล็ดลับแนวปฏิบัติที่ดีที่สุดคำศัพท์และอื่น ๆ ที่สามารถช่วยฉันได้

ใช้รหัสด้านล่าง ฉันเขียนรหัสนี้ โดยมีจุดมุ่งหมายที่จะกำหนดความคล้ายคลึงกันร้อยละระหว่างวัตถุสองระหว่างการพูดและ{a:1, b:2, c:3, d:3} {a:1, b:1, e:2, f:2, g:3, h:5}ผมผลิตรหัสในการตอบสนองต่อคำถามนี้ในกองมากเกิน เพราะฉันไม่แน่ใจเหมือนกันว่าเปอร์เซ็นต์ที่คล้ายคลึงกันที่โปสเตอร์ถามเกี่ยวกับอะไรฉันจึงให้สี่ชนิดที่แตกต่างกัน:

  • เปอร์เซ็นต์ของกุญแจในวัตถุที่ 1 ที่สามารถพบได้ในที่ 2
  • ร้อยละของค่าในวัตถุที่ 1 ที่สามารถพบได้ใน 2 รวมถึงการทำซ้ำ
  • เปอร์เซ็นต์ของค่าในวัตถุที่ 1 ที่สามารถพบได้ในที่ 2 โดยไม่อนุญาตให้ทำซ้ำและ
  • เปอร์เซ็นต์ของคู่ {key: value} ในวัตถุที่ 1 ที่สามารถพบได้ในวัตถุที่สอง

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

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

ดังนั้นทุกคนสามารถบอกฉันได้ว่า "อ่าน" บางส่วนของรหัสที่ซับซ้อนมากขึ้นในทางที่กระชับและมีส่วนช่วยให้ฉันเข้าใจว่าฉันกำลังอ่านอะไรอยู่? ฉันเดาว่าชิ้นส่วนที่ทำให้ฉันได้มากที่สุดคือชิ้นส่วนที่มีลูกศรอ้วนหลายอันติดกันและ / หรือชิ้นส่วนที่มีวงเล็บหลายอันติดกัน อีกครั้งที่แกนกลางของพวกเขาในที่สุดฉันก็สามารถหาเหตุผลได้ แต่ (ฉันหวังว่า) มีวิธีที่ดีกว่าในการดำเนินการอย่างรวดเร็วและชัดเจนและตรงไปตรงมา "รับ" การเขียนโปรแกรมจาวาสคริปต์ที่ใช้งานได้

อย่าลังเลที่จะใช้รหัสบรรทัดใด ๆ จากด้านล่างหรือแม้แต่ตัวอย่างอื่น ๆ อย่างไรก็ตามหากคุณต้องการคำแนะนำเบื้องต้นจากฉันนี่คือบางส่วน เริ่มด้วยสิ่งที่เรียบง่ายอย่างมีเหตุผล obj => key => obj[key]จากใกล้ถึงจุดสิ้นสุดของรหัสที่มีนี้ที่ถูกส่งผ่านเป็นพารามิเตอร์ไปยังฟังก์ชัน: คนเราอ่านและเข้าใจได้อย่างไร const getXs = (obj, getX) => Object.keys(obj).map(key => getX(obj)(key));ตัวอย่างอีกต่อไปเป็นหนึ่งในฟังก์ชั่นเต็มรูปแบบจากที่อยู่ใกล้กับจุดเริ่มต้น: mapส่วนสุดท้ายทำให้ฉันโดยเฉพาะอย่างยิ่ง

โปรดทราบว่า ณ เวลานี้ฉันไม่ได้มองหาการอ้างอิงถึง Haskell หรือสัญกรณ์นามธรรมสัญลักษณ์หรือพื้นฐานของการแก้เผ็ด ฯลฯสิ่งที่ฉันกำลังมองหาคือประโยคภาษาอังกฤษที่ฉันสามารถพูดอย่างเงียบ ๆ ในขณะที่มองหาบรรทัดของรหัส หากคุณมีการอ้างอิงที่เจาะจงเป็นพิเศษนั้นยอดเยี่ยม แต่ฉันก็ไม่ได้มองหาคำตอบที่บอกว่าฉันควรไปอ่านตำราพื้นฐาน ฉันทำไปแล้วและฉันก็ได้รับตรรกะอย่างน้อยก็อย่างน้อย โปรดทราบว่าฉันไม่ต้องการคำตอบที่ละเอียดถี่ถ้วน (แม้ว่าจะยินดีต้อนรับความพยายามดังกล่าว): แม้คำตอบสั้น ๆ ที่ให้วิธีการที่ยอดเยี่ยมในการอ่านบรรทัดหนึ่งบรรทัดของรหัสที่มีปัญหาอย่างอื่นจะได้รับการชื่นชม

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

เคล็ดลับใด ๆ ที่จะได้รับการชื่นชม

const obj1 = { a:1, b:2, c:3, d:3 };
const obj2 = { a:1, b:1, e:2, f:2, g:3, h:5 };

// x or X is key or value or key/value pair

const getXs = (obj, getX) =>
  Object.keys(obj).map(key => getX(obj)(key));

const getPctSameXs = (getX, filter = vals => vals) =>
  (objA, objB) =>
    filter(getXs(objB, getX))
      .reduce(
        (numSame, x) =>
          getXs(objA, getX).indexOf(x) > -1 ? numSame + 1 : numSame,
        0
      ) / Object.keys(objA).length * 100;

const pctSameKeys       = getPctSameXs(obj => key => key);
const pctSameValsDups   = getPctSameXs(obj => key => obj[key]);
const pctSameValsNoDups = getPctSameXs(obj => key => obj[key], vals => [...new Set(vals)]);
const pctSameProps      = getPctSameXs(obj => key => JSON.stringify( {[key]: obj[key]} ));

console.log('obj1:', JSON.stringify(obj1));
console.log('obj2:', JSON.stringify(obj2));
console.log('% same keys:                   ', pctSameKeys      (obj1, obj2));
console.log('% same values, incl duplicates:', pctSameValsDups  (obj1, obj2));
console.log('% same values, no duplicates:  ', pctSameValsNoDups(obj1, obj2));
console.log('% same properties (k/v pairs): ', pctSameProps     (obj1, obj2));

// output:
// obj1: {"a":1,"b":2,"c":3,"d":3}
// obj2: {"a":1,"b":1,"e":2,"f":2,"g":3,"h":5}
// % same keys:                    50
// % same values, incl duplicates: 125
// % same values, no duplicates:   75
// % same properties (k/v pairs):  25

คำตอบ:


18

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

function mapObj(obj, f) {
  return Object.keys(obj).map(key => f(obj, key));
}

function getPctSameXs(obj1, obj2, f) {
  const mapped1 = mapObj(obj1, f);
  const mapped2 = mapObj(obj2, f);
  const same = mapped1.filter(x => mapped2.indexOf(x) != -1);
  const percent = same.length / mapped1.length * 100;
  return percent;
}

const getValues = (obj, key) => obj[key];
const valuesWithDupsPercent = getPctSameXs(obj1, obj2, getValues);

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

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

หากคุณเจอโค้ดที่อ่านยากให้ทำการ refactor มันจนกว่าจะง่ายขึ้น ต้องฝึกฝนบ้าง แต่ก็คุ้มค่า รหัสฟังก์ชั่นสามารถอ่านได้เท่าที่จำเป็น ในความเป็นจริงมัก moreso เพราะมันมักจะรัดกุมมากขึ้น


ไม่มีความผิดแน่นอน! ในขณะที่ฉันจะยังคงรักษาที่ฉันรู้บางสิ่งเกี่ยวกับการเขียนโปรแกรมการทำงานบางทีการยืนยันของฉันในคำถามเกี่ยวกับเท่าใดฉันรู้ว่าเป็นเพียงเล็กน้อยที่ระบุไว้ ฉันเป็นผู้เริ่มต้นสัมพัทธ์จริงๆ ดังนั้นการเห็นว่าความพยายามของฉันในเรื่องนี้สามารถเขียนใหม่ได้อย่างไรในรูปแบบที่ชัดเจน แต่ก็ยังใช้งานได้ดูเหมือนว่าเป็นทองคำ ... ขอบคุณ ฉันจะศึกษาการเขียนใหม่ของคุณอย่างระมัดระวัง
Andrew Willems

1
ฉันได้ยินมาว่ามีโซ่ยาวและ / หรือการซ้อนเมธอดช่วยลดตัวแปรกลางที่ไม่จำเป็น ในทางตรงกันข้ามคำตอบของคุณแบ่งโซ่ / การซ้อนของฉันออกเป็นงบกลางแบบสแตนด์อะโลนโดยใช้ตัวแปรกลางที่มีชื่อดี ฉันพบรหัสของคุณอ่านได้มากขึ้นในกรณีนี้ แต่ฉันสงสัยว่าคุณกำลังพยายามเป็นแบบไหน คุณกำลังบอกว่าโซ่ยาววิธีการและ / หรือการทำรังลึกมักจะหรือแม้กระทั่งรูปแบบการต่อต้านที่จะหลีกเลี่ยงหรือมักจะมีเวลาที่พวกเขานำผลประโยชน์ที่สำคัญ? และคำตอบสำหรับคำถามนั้นแตกต่างกันสำหรับฟังก์ชั่นและการเข้ารหัสที่จำเป็นหรือไม่?
Andrew Willems

3
มีบางสถานการณ์ที่การกำจัดตัวแปรกลางสามารถเพิ่มความชัดเจนได้ ตัวอย่างเช่นใน FP คุณแทบไม่ต้องการดัชนีลงในอาร์เรย์ บางครั้งก็ไม่มีชื่อที่ดีสำหรับผลลัพธ์ระดับกลาง ในประสบการณ์ของฉันแม้ว่าคนส่วนใหญ่มักจะผิดพลาดไปไกลเกินไป
Karl Bielefeldt

6

ฉันไม่ได้ทำมากของการทำงานที่มีประสิทธิภาพสูงใน Javascript (ซึ่งผมจะบอกว่านี้เป็น - คนส่วนใหญ่พูดคุยเกี่ยวกับการทำงาน Javascript อาจจะมีการใช้แผนที่กรองและลด แต่รหัสของคุณกำหนดฟังก์ชั่นในระดับที่สูงขึ้นของตัวเองซึ่งเป็น ค่อนข้างสูงกว่านั้น) แต่ฉันทำไปแล้วใน Haskell และฉันคิดว่าอย่างน้อยประสบการณ์บางอย่างก็แปล ฉันจะให้คำแนะนำคุณสองสามข้อกับสิ่งที่ฉันได้เรียนรู้:

การระบุประเภทของฟังก์ชั่นนั้นสำคัญมาก Haskell ไม่ต้องการให้คุณระบุประเภทของฟังก์ชัน แต่การรวมประเภทในคำจำกัดความทำให้อ่านง่ายขึ้น แม้ว่า Javascript จะไม่รองรับการพิมพ์ที่ชัดเจนในลักษณะเดียวกัน แต่ก็ไม่มีเหตุผลที่จะไม่รวมคำจำกัดความประเภทไว้ในความคิดเห็นเช่น:

// getXs :: forall O, F . O -> (O -> String -> F) -> [F]
const getXs = (obj, getX) =>
    Object.keys(obj).map(key => getX(obj)(key));

ด้วยการฝึกฝนเล็กน้อยในการทำงานกับคำจำกัดความประเภทเช่นนี้พวกเขาทำให้ความหมายของฟังก์ชันชัดเจนขึ้น

การตั้งชื่อมีความสำคัญบางทีอาจมากกว่าในขั้นตอนการโปรแกรม โปรแกรมการทำงานจำนวนมากถูกเขียนในรูปแบบสั้น ๆ ที่หนักในการประชุม (เช่นการประชุมที่ 'xs' เป็นลิสต์ / อาเรย์และ 'x' เป็นไอเท็มที่มันแพร่หลายมาก ) แต่ถ้าคุณไม่เข้าใจสไตล์นั้น ง่ายฉันขอแนะนำการตั้งชื่อ verbose เพิ่มเติม การดูชื่อเฉพาะที่คุณใช้ "getX" นั้นเป็นแบบทึบและดังนั้น "getXs" ก็ไม่ได้ช่วยอะไรมากนัก ฉันจะเรียก "getXs" บางอย่างเช่น "ApplyToProperties" และ "getX" น่าจะเป็น "propertyMapper" "getPctSameXs" จะเป็น "percentPropertiesSameWith" ("กับ")

อีกสิ่งที่สำคัญคือการเขียนโค้ดสำนวน ฉันสังเกตเห็นว่าคุณกำลังใช้ไวยากรณ์a => b => some-expression-involving-a-and-bเพื่อสร้างฟังก์ชั่น curried สิ่งนี้น่าสนใจและอาจมีประโยชน์ในบางสถานการณ์แต่คุณไม่ได้ทำอะไรที่นี่ซึ่งได้รับประโยชน์จากฟังก์ชั่น curriedและมันจะเป็นจาวาสคริปต์ที่ใช้สำนวนมากกว่าการใช้ฟังก์ชั่นหลายอาร์กิวเมนต์แบบดั้งเดิมแทน การทำเช่นนั้นอาจช่วยให้มองเห็นสิ่งที่เกิดขึ้นได้ง่ายขึ้น คุณยังใช้const name = lambda-expressionเพื่อกำหนดฟังก์ชั่นซึ่งมันจะใช้สำนวนfunction name (args) { ... }แทนมากกว่า ฉันรู้ว่ามันมีความแตกต่างทางความหมายเล็กน้อย แต่ถ้าคุณไม่ได้พึ่งพาความแตกต่างเหล่านั้นฉันขอแนะนำให้ใช้ตัวแปรทั่วไปมากขึ้นเมื่อทำได้


5
+1 สำหรับประเภท! เพียงเพราะภาษาที่ไม่ได้มีพวกเขาไม่ได้หมายความว่าคุณไม่ต้องคิดเกี่ยวกับพวกเขา ระบบเอกสารจำนวนมากสำหรับ ECMAScript มีภาษาประเภทสำหรับการบันทึกประเภทของฟังก์ชั่น IDEs ECMAScript หลายชนิดมีภาษาเป็นอย่างดี (และมักจะพวกเขายังเข้าใจภาษาชนิดสำหรับระบบเอกสารที่สำคัญ) และพวกเขายังสามารถดำเนินการตรวจสอบประเภทพื้นฐานและเค้าแก้ปัญหาโดยใช้คำอธิบายประกอบประเภทเหล่านั้น
Jörg W Mittag

คุณให้ฉันเคี้ยวเยอะมาก: นิยามประเภทชื่อที่มีความหมายใช้สำนวน ... ขอบคุณ! เพียงไม่กี่ความเห็นที่เป็นไปได้: ฉันไม่ได้ตั้งใจจะเขียนบางส่วนเป็นฟังก์ชั่น curated; พวกเขาเพียงแค่พัฒนาวิธีนั้นเมื่อฉันปรับโครงสร้างรหัสของฉันใหม่ในระหว่างการเขียน ฉันสามารถดูได้ว่าวิธีนี้ไม่จำเป็นต้องใช้และแม้แต่การรวมพารามิเตอร์จากทั้งสองฟังก์ชั่นเป็นสองพารามิเตอร์สำหรับฟังก์ชั่นเดียวไม่เพียง แต่ทำให้มีเหตุผลมากขึ้น แต่ทันทีทำให้สั้นที่อ่านง่ายขึ้น
Andrew Willems

@ JörgWMittagขอบคุณสำหรับความคิดเห็นของคุณเกี่ยวกับความสำคัญของประเภทและลิงค์ไปยังคำตอบอื่น ๆ ที่คุณเขียน ฉันใช้ WebStorm และไม่ทราบว่าตามที่ฉันอ่านคำตอบอื่น ๆ ของคุณ WebStorm รู้วิธีตีความ jsdoc เหมือนคำอธิบายประกอบ ฉันสมมติว่าจากความคิดเห็นของคุณที่ jsdoc และ WebStorm สามารถใช้ร่วมกันเพื่ออธิบายการทำงานไม่เพียง แต่จำเป็นรหัส แต่ฉันต้องเจาะลึกเพิ่มเติมเพื่อทราบจริง ๆ ว่า ฉันเคยเล่นกับ jsdoc มาก่อนและตอนนี้ฉันรู้แล้วว่า WebStorm และฉันสามารถร่วมมือกันได้ฉันคาดหวังว่าฉันจะใช้คุณสมบัติ / วิธีการนั้นมากกว่านี้
Andrew Willems

@Jules เพียงเพื่อชี้แจงว่าฟังก์ชั่น curried ใดที่ฉันอ้างถึงในความคิดเห็นของฉันด้านบน: ตามที่คุณบอกเป็นนัยแต่ละอินสแตนซ์ของobj => key => ...สามารถทำให้เป็นภาษาจีนได้ง่ายขึ้น(obj, key) => ...เพราะในภายหลังgetX(obj)(key)สามารถทำให้ง่ายget(obj, key)ขึ้น ในทางตรงกันข้ามฟังก์ชั่นอื่นที่ curried (getX, filter = vals => vals) => (objA, objB) => ...ไม่สามารถทำให้ง่ายขึ้นอย่างน้อยก็ในบริบทของส่วนที่เหลือของรหัสตามที่เขียน
Andrew Willems
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.