ข้อดีของการเขียนโปรแกรมไร้สัญชาติ?


132

เมื่อเร็ว ๆ นี้ฉันได้เรียนรู้เกี่ยวกับการเขียนโปรแกรมเชิงฟังก์ชัน (โดยเฉพาะ Haskell แต่ฉันได้อ่านบทเรียนเกี่ยวกับ Lisp และ Erlang แล้วด้วย) ในขณะที่ฉันพบว่าแนวคิดนี้ให้ความกระจ่างมาก แต่ฉันก็ยังไม่เห็นด้านที่เป็นประโยชน์ของแนวคิด "ไม่มีผลข้างเคียง" ข้อดีของมันคืออะไร? ฉันพยายามคิดในแนวความคิดที่ใช้งานได้ แต่มีบางสถานการณ์ที่ดูซับซ้อนเกินไปโดยไม่สามารถบันทึกสถานะได้ด้วยวิธีง่ายๆ (ฉันไม่คิดว่า monads ของ Haskell 'ง่าย')

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

ฉันสนใจประสิทธิภาพน้อยกว่าประสิทธิภาพการทำงาน ดังนั้นฉันจึงถามเป็นหลักว่าฉันจะทำงานได้ดีกว่าในภาษาที่ใช้งานได้ดีกว่าขั้นตอน / เชิงวัตถุ / อะไรก็ตาม

คำตอบ:


168

อ่านหน้าที่ Programming ในกะลา

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

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


5
ฉันสองนี่! ฉันเชื่อว่าการเขียนโปรแกรมเชิงฟังก์ชันจะถูกนำมาใช้อย่างแพร่หลายมากขึ้นในอนาคตเนื่องจากความเหมาะสมกับการเขียนโปรแกรมแบบขนาน
Ray Hidayat

@Ray: ฉันจะเพิ่มโปรแกรมแบบกระจายด้วย!
Anton Tykhyy

ส่วนใหญ่เป็นจริงยกเว้นการดีบัก โดยทั่วไปจะยากกว่าใน haskell เนื่องจากคุณไม่มี call stack จริงเป็นเพียงสแต็กที่จับคู่รูปแบบ และยากกว่ามากที่จะคาดเดาว่าโค้ดของคุณลงเอยด้วยอะไร
hasufell

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

2
ไม่ชอบการเทียบเคียงการไร้สัญชาติกับ FP โปรแกรม FP จำนวนมากเต็มไปด้วยสถานะมันมีอยู่ในการปิดแทนที่จะเป็นวัตถุ
mikemaccana

46

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

คุณสามารถดูบทช่วยสอนที่ดีพร้อมตัวอย่างมากมายได้ในเอกสารของ John Hughes เหตุใดจึงต้องมีการเขียนโปรแกรมเชิงฟังก์ชัน (PDF)

คุณจะgobsมีประสิทธิผลมากขึ้นโดยเฉพาะอย่างยิ่งหากคุณเลือกภาษาทำงานที่ยังมีชนิดข้อมูลเกี่ยวกับพีชคณิตและจับคู่รูปแบบ (Caml, SML, Haskell)


มิกซ์อินจะไม่ให้โค้ดที่ใช้ซ้ำได้ในลักษณะเดียวกันกับ OOP หรือไม่? ไม่สนับสนุน OOP เพียงแค่พยายามเข้าใจสิ่งต่างๆด้วยตัวเอง
mikemaccana

20

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

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

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

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

let matchingFactors =
    factors
    |> Seq.filter (fun x -> largestPalindrome % x = 0)
    |> Seq.map (fun x -> (x, largestPalindrome / x))

ฉันรู้ว่าการทำตัวกรองแล้วแผนที่ (นั่นคือการแปลงของแต่ละองค์ประกอบ) ใน C # นั้นค่อนข้างง่าย แต่คุณต้องคิดในระดับที่ต่ำกว่า โดยเฉพาะอย่างยิ่งคุณจะต้องเขียนลูปเองและมีคำสั่ง if ที่ชัดเจนของคุณเองและสิ่งเหล่านั้น ตั้งแต่เรียนรู้ F # ฉันรู้ว่าฉันพบว่าการเขียนโค้ดในรูปแบบการทำงานง่ายขึ้นโดยที่ถ้าคุณต้องการกรองคุณต้องเขียน "ตัวกรอง" และถ้าคุณต้องการแมปคุณเขียน "แผนที่" แทนที่จะใช้ แต่ละรายละเอียด

ฉันชอบตัวดำเนินการ |> ซึ่งฉันคิดว่าแยก F # ออกจาก ocaml และอาจเป็นภาษาที่ใช้งานได้อื่น ๆ มันเป็นตัวดำเนินการไปป์ซึ่งช่วยให้คุณ "ไพพ์" เอาต์พุตของนิพจน์หนึ่งไปยังอินพุตของนิพจน์อื่น มันทำให้โค้ดเป็นไปตามวิธีคิดของฉันมากขึ้น เช่นเดียวกับในข้อมูลโค้ดด้านบนที่กล่าวว่า "ใช้ลำดับปัจจัยกรองแล้วแมป" เป็นการคิดระดับสูงมากซึ่งคุณไม่ได้ใช้ภาษาโปรแกรมที่จำเป็นเพราะคุณยุ่งอยู่กับการเขียน loop และ if statement เป็นสิ่งหนึ่งที่ฉันคิดถึงมากที่สุดเมื่อใดก็ตามที่ฉันใช้ภาษาอื่น

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

แก้ไข : ฉันเห็นหนึ่งในความคิดเห็นที่คุณขอตัวอย่างของ "state" ในภาษาโปรแกรมที่ใช้งานได้ F # สามารถเขียนได้ตามความจำเป็นดังนั้นนี่คือตัวอย่างโดยตรงของวิธีที่คุณสามารถมีสถานะที่ไม่แน่นอนใน F #:

let mutable x = 5
for i in 1..10 do
    x <- x + i

1
ฉันเห็นด้วยกับโพสต์ของคุณโดยทั่วไป แต่ |> ไม่มีส่วนเกี่ยวข้องกับการเขียนโปรแกรมเชิงฟังก์ชันต่อ se ที่จริงแล้วเป็นเพียงน้ำตาลประโยคสำหรับa |> b (p1, p2) b (a, p1, p2)จับคู่สิ่งนี้ด้วยการเชื่อมโยงที่ถูกต้องและคุณได้รับมัน
Anton Tykhyy

2
จริงอยู่ฉันควรรับทราบว่าประสบการณ์เชิงบวกของฉันกับ F # อาจเกี่ยวข้องกับ F # มากกว่าการเขียนโปรแกรมเชิงฟังก์ชัน แต่ถึงกระนั้นก็ยังมีความสัมพันธ์ที่แข็งแกร่งระหว่างทั้งสองและแม้ว่าสิ่งต่างๆเช่นการอนุมานประเภทและ |> จะไม่ใช่การเขียนโปรแกรมเชิงฟังก์ชัน แต่อย่างใดฉันก็จะอ้างว่าพวกเขา "ไปกับดินแดน" อย่างน้อยโดยทั่วไป
Ray Hidayat

|> เป็นเพียงฟังก์ชัน infix ลำดับที่สูงกว่าในกรณีนี้คือตัวดำเนินการแอปพลิเคชันฟังก์ชัน การกำหนดลำดับที่สูงกว่าของคุณเองผู้ประกอบการมัดเป็นแน่นอนส่วนหนึ่งของโปรแกรมการทำงาน (ถ้าคุณเป็นอุบาย) Haskell มี $ ซึ่งเหมือนกันยกเว้นข้อมูลในไปป์ไลน์จะไหลจากขวาไปซ้าย
Norman Ramsey

15

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

ตอนนี้มีจุดบกพร่องจำนวนเท่าใดที่เกิดจาก "การโต้ตอบโดยไม่ได้ตั้งใจ" ระหว่างสององค์ประกอบที่แยกจากกันของโปรแกรม (เกือบทุกข้อบกพร่องเกลียวมีรูปแบบนี้ที่เกี่ยวข้องกับการแข่งขันการเขียนข้อมูลที่ใช้ร่วมกันนะคะ ... นอกจากนี้มันเป็นเรื่องธรรมดาที่จะหาห้องสมุดที่มีผลที่ไม่คาดคิดบางอย่างเกี่ยวกับรัฐทั่วโลกหรืออ่าน / เขียนรีจิสทรี / สิ่งแวดล้อม ฯลฯ ) ฉันจะวางว่าอย่างน้อย 1 ใน 3 ของ 'ฮาร์ดบั๊ก' ตกอยู่ในหมวดหมู่นี้

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

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


6
มีคนเคยกล่าวไว้ว่าการเขียนทับค่าที่ไม่แน่นอนหมายความว่าคุณกำลังเก็บขยะ / กำจัดค่าก่อนหน้านี้อย่างชัดเจน ในบางกรณีส่วนอื่น ๆ ของโปรแกรมไม่ได้ใช้ค่านั้น เมื่อไม่สามารถกลายพันธุ์ค่าได้บั๊กคลาสนี้ก็จะหายไปด้วย
ชา เม.ย.

8

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

แต่ประสิทธิภาพไม่ใช่สิ่งเดียวที่ต้องกังวล ฟังก์ชันบริสุทธิ์นั้นง่ายต่อการทดสอบและแก้ไขข้อบกพร่องเนื่องจากมีการระบุสิ่งที่มีผลกระทบอย่างชัดเจน และเมื่อเขียนโปรแกรมด้วยภาษาที่ใช้งานได้คน ๆ หนึ่งจะติดนิสัยที่จะสร้างฟังก์ชันที่ "สกปรก" (ด้วย I / O เป็นต้น) ให้น้อยที่สุดเท่าที่จะทำได้ วิธีนี้เป็นวิธีที่ดีในการออกแบบโปรแกรมแม้ในภาษาที่ใช้งานไม่ได้

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


ส่วนแรกนั้นเป็นจุดที่น่าสนใจจริงๆฉันไม่เคยคิดมาก่อน ขอบคุณ!
Sasha Chedygov

สมมติว่าคุณมีsin(PI/3)โค้ดของคุณโดยที่ PI เป็นค่าคงที่คอมไพเลอร์สามารถประเมินฟังก์ชันนี้ในเวลาคอมไพล์และฝังผลลัพธ์ลงในโค้ดที่สร้างขึ้น
Artelius

6

หากไม่มีสถานะมันเป็นเรื่องง่ายมากที่จะขนานโค้ดของคุณโดยอัตโนมัติ (เนื่องจากซีพียูถูกสร้างขึ้นโดยมีคอร์มากขึ้นสิ่งนี้สำคัญมาก)


ใช่ฉันได้ตรวจสอบแล้ว โมเดลการทำงานพร้อมกันของ Erlang นั้นน่าสนใจมาก อย่างไรก็ตาม ณ จุดนี้ฉันไม่สนใจเกี่ยวกับการทำงานพร้อมกันมากเท่ากับประสิทธิภาพการทำงาน มีโบนัสผลผลิตจากการเขียนโปรแกรมโดยไม่มีรัฐหรือไม่?
Sasha Chedygov

2
@musicfreak ไม่มีโบนัสเพิ่มประสิทธิภาพ แต่โปรดทราบว่าภาษา FP สมัยใหม่ยังคงให้คุณใช้ state ได้หากคุณต้องการจริงๆ
Unknown

จริงๆ? คุณสามารถยกตัวอย่างสถานะในภาษาที่ใช้งานได้เพื่อที่ฉันจะได้เห็นว่ามันเป็นอย่างไร
Sasha Chedygov

ตรวจสอบ State Monad ใน Haskell - book.realworldhaskell.org/read/monads.html#x_NZ
rampion

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

6

เว็บแอปพลิเคชันไร้สัญชาติเป็นสิ่งสำคัญเมื่อคุณเริ่มมีการเข้าชมสูง

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

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

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


5

ผมเขียนโพสต์บนเพียงแค่เรื่องนี้เดี๋ยวกลับ: ในความสำคัญของความบริสุทธิ์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.