portaldacalheta.pt
  • หลัก
  • เทคโนโลยี
  • การจัดการวิศวกรรม
  • ผู้คนและทีมงาน
  • ส่วนหลัง
ส่วนหลัง

หลักการความรับผิดชอบเดียว: สูตรสำหรับหลักจรรยาบรรณ



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

หลักการความรับผิดชอบเดียว: สูตรสำหรับหลักจรรยาบรรณ



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



โมเดลคือทุกสิ่ง

หนังสือเกือบทุกเล่มเกี่ยวกับเฟรมเวิร์ก MVC ใหม่ (MVP, MVVM หรือ M ** อื่น ๆ ) เต็มไปด้วยตัวอย่างโค้ดที่ไม่ดี ตัวอย่างเหล่านี้พยายามแสดงให้เห็นว่ากรอบมีอะไรให้บ้าง แต่พวกเขายังให้คำแนะนำที่ไม่ดีสำหรับผู้เริ่มต้น ตัวอย่างเช่น“ สมมติว่าเรามี ORM X นี้สำหรับโมเดลของเราซึ่งเป็นเครื่องมือแม่แบบ ย สำหรับมุมมองของเราดังนั้นเราจะมีตัวควบคุมเพื่อจัดการทุกอย่าง” พวกเขาไม่ได้ทำอะไรเลยนอกจากตัวควบคุมขนาดมหึมา อย่างไรก็ตามในการป้องกันหนังสือเหล่านี้ตัวอย่างมีขึ้นเพื่อแสดงให้เห็นถึงความสะดวกในการใช้กรอบงานของหนังสือเหล่านี้ พวกเขาไม่ได้มีไว้เพื่อสอนการออกแบบซอฟต์แวร์ แต่ผู้อ่านที่ติดตามตัวอย่างเหล่านี้ตระหนักดีว่าหลังจากหลายปีที่ผ่านมาการต่อต้านการมีโค้ดชิ้นเดียวในโครงการของคุณเป็นอย่างไร



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



ในขณะที่เราดำเนินการต่อในบทความนี้คุณจะรู้ว่าหลักสำคัญคือการแยกความกังวลออกจากกัน

หลักการความรับผิดชอบเดียว

คุณคงเคยได้ยินหลักการ ของแข็ง : ความรับผิดเดียว, เปิด - ปิด, การทดแทน liskov, การแยกส่วนต่อประสานและการกลับรายการการอ้างอิง อักษรตัวแรก S หมายถึงหลักการความรับผิดชอบเดียว ( เคียว ) และความสำคัญของมันไม่สามารถคุยโวได้ ฉันจะบอกว่ามันเป็นเงื่อนไขที่จำเป็นและสำคัญสำหรับรหัสที่ดี ในความเป็นจริงในโค้ดใด ๆ ที่เขียนไม่ดีคุณสามารถค้นหาคลาสที่มีความรับผิดชอบมากกว่าหนึ่งเสมอ - form1.cs หรือ index.php ซึ่งมีโค้ดไม่กี่พันบรรทัดไม่ใช่เรื่องแปลกและพวกเราทุกคนอาจเคยเห็น หรือเสร็จสิ้น



ลองดูตัวอย่างใน C # (ASP.NET MVC และ Entity framework) แม้ว่าคุณจะไม่ได้เป็น นักพัฒนา C # ด้วยประสบการณ์ OOP เล็กน้อยคุณสามารถก้าวไปข้างหน้าได้อย่างง่ายดาย

public class OrderController { ... public ActionResult CreateForm() { /* * View data preparations */ return View(); } [HttpPost] public ActionResult Create(OrderCreateRequest request) { if (!ModelState.IsValid) { /* * View data preparations */ return View(); } using (var context = new DataContext()) { var order = new Order(); // Create order from request context.Orders.Add(order); // Reserve ordered goods …(Huge logic here)... context.SaveChanges(); //Send email with order details for customer } return RedirectToAction('Index'); } ... (many more methods like Create here) }

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



บัตรเครดิตที่ถูกแฮ็กด้วย cvv

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



แต่วันนี้เป็นวันที่เราหยุดเขียนไดรเวอร์ขนาดมหึมาเหล่านี้!

ก่อนอื่นเรามาแยกตรรกะทางธุรกิจทั้งหมดจากคอนโทรลเลอร์แล้วย้ายไปที่คลาส OrderService :



public class OrderService { public void Create(OrderCreateRequest request) { // all actions for order creating here } } public class OrderController { public OrderController() { this.service = new OrderService(); } [HttpPost] public ActionResult Create(OrderCreateRequest request) { if (!ModelState.IsValid) { /* * View data preparations */ return View(); } this.service.Create(request); return RedirectToAction('Index'); }

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

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

นี่คือ เคียว และมีเทคนิคมากมายในการเขียนโค้ดที่ตรงตามหลักการนี้ ตัวอย่างนี้คือการฉีดแบบพึ่งพา (สิ่งที่มีประโยชน์เช่นกัน เขียนโค้ดที่ทดสอบได้ ).

การฉีดพึ่งพา

เป็นการยากที่จะจินตนาการถึงโครงการขนาดใหญ่ตามหลักการความรับผิดชอบเดียวโดยไม่มีการพึ่งพาอาศัยกัน (Dependency Injection) ลองมาดูชั้นเรียนของเราอีกครั้ง OrderService :

public class OrderService { public void Create(...) { // Creating the order(and let’s forget about reserving here, it’s not important for following examples) // Sending an email to client with order details var smtp = new SMTP(); // Setting smtp.Host, UserName, Password and other parameters smtp.Send(); } }

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

public class OrderService { private SmtpMailer mailer; public OrderService() { this.mailer = new SmtpMailer(); } public void Create(...) { // Creating the order // Sending an email to client with order details this.mailer.Send(...); } } public class SmtpMailer { public void Send(string to, string subject, string body) { // SMTP stuff will be only here } }

ดีกว่า! แต่ชั้นเรียน OrderService คุณยังรู้มากมายเกี่ยวกับการส่งอีเมล คุณต้องการชั้นเรียนอย่างแม่นยำ SmtpMailer เพื่อส่งอีเมล จะเป็นอย่างไรหากเราต้องการเปลี่ยนแปลงในภายหลัง จะเป็นอย่างไรหากเราต้องการพิมพ์เนื้อหาของอีเมลที่ส่งไปยังไฟล์บันทึกพิเศษแทนที่จะส่งในสภาพแวดล้อมการพัฒนาของเรา จะเป็นอย่างไรหากเราต้องการทดสอบชั้นเรียน OrderService เหรอ? มาดำเนินการต่อด้วยการสร้างอินเทอร์เฟซ IMailer :

public interface IMailer { void Send(string to, string subject, string body); }

SmtpMailer จะใช้อินเทอร์เฟซนี้ นอกจากนี้แอปพลิเคชันของเราจะใช้คอนเทนเนอร์ IoC และเราสามารถกำหนดค่าได้ IMailer ดำเนินการโดยชั้นเรียน SmtpMailer . OrderService สามารถเปลี่ยนแปลงได้ดังนี้:

เลขบัตรเครดิตประชาชนจริง
public sealed class OrderService: IOrderService { private IOrderRepository repository; private IMailer mailer; public OrderService(IOrderRepository repository, IMailer mailer) { this.repository = repository; this.mailer = mailer; } public void Create(...) { var order = new Order(); // fill the Order entity using the full power of our Business Logic(discounts, promotions, etc.) this.repository.Save(order); this.mailer.Send(, , ); } }

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

ด้วยวิธีนี้หากผู้ทดสอบพบสิ่งผิดปกติในการส่งอีเมลนักพัฒนาจะรู้ว่าต้องดูที่ไหน: ชั้นเรียน SmtpMailer . หากมีบางอย่างผิดปกติกับส่วนลดนักพัฒนาจะรู้อีกครั้งว่าควรดูที่ไหน: รหัสชั้นเรียน OrderService (หรือในกรณีที่คุณยอมรับ เคียว จากใจจริงก็อาจเป็นได้ DiscountService ).

สถาปัตยกรรมที่ขับเคลื่อนด้วยเหตุการณ์

อย่างไรก็ตามฉันยังไม่ชอบวิธี OrderService.Create:

public void Create(...) { var order = new Order(); ... this.repository.Save(order); this.mailer.Send(, , ); }

การส่งอีเมลไม่ได้เป็นส่วนหนึ่งของขั้นตอนการสร้างคำสั่งซื้อหลัก แม้ว่าแอปพลิเคชันจะส่งอีเมลไม่สำเร็จคำสั่งซื้อยังคงถูกสร้างขึ้นอย่างถูกต้อง ลองนึกภาพสถานการณ์ที่คุณต้องเพิ่มตัวเลือกใหม่ในพื้นที่การตั้งค่าผู้ใช้ที่อนุญาตให้พวกเขาเลือกไม่รับอีเมลหลังจากสั่งซื้อสำเร็จ เพื่อรวมสิ่งนี้เข้ากับชั้นเรียนของเรา OrderService เราจะต้องแนะนำการพึ่งพา: IUserParametersService . เพิ่มสถานที่และคุณมีการอ้างอิงใหม่แล้ว ตัวแปลภาษา (เพื่อสร้างอีเมลที่ถูกต้องในภาษาที่ผู้ใช้เลือก) การกระทำหลายอย่างเหล่านี้ไม่จำเป็นโดยเฉพาะอย่างยิ่งแนวคิดในการเพิ่มการอ้างอิงจำนวนมากและลงท้ายด้วยตัวสร้างที่ไม่พอดีกับหน้าจอ ฉันพบไฟล์ ตัวอย่างที่ดี ของสิ่งนี้ใน Magento codebase (ก CMS ซอฟต์แวร์อีคอมเมิร์ซที่เขียนด้วย PHP) ในคลาสที่มีการอ้างอิง 32 รายการ!

บางครั้งก็ยากที่จะจินตนาการว่าจะแยกตรรกะนี้ออกไปได้อย่างไรและคลาส Magento ก็น่าจะเป็นเหยื่อของหนึ่งในกรณีเหล่านั้น นั่นเป็นเหตุผลที่ฉันชอบวิธี 'ตามเหตุการณ์':

namespace .Events { [Serializable] public class OrderCreated { private readonly Order order; public OrderCreated(Order order) { this.order = order; } public Order GetOrder() { return this.order; } } }

ทุกครั้งที่มีการสร้างคำสั่งแทนที่จะส่งอีเมลจากชั้นเรียนโดยตรง OrderService คลาสกิจกรรมพิเศษถูกสร้างขึ้น OrderCreated และเหตุการณ์จะถูกสร้างขึ้น ที่ใดที่หนึ่งในตัวจัดการเหตุการณ์ของแอปพลิเคชันจะได้รับการกำหนดค่า หนึ่งในนั้นจะส่งอีเมลไปยังลูกค้า

namespace .EventHandlers { public class OrderCreatedEmailSender : IEventHandler { public OrderCreatedEmailSender(IMailer, IUserParametersService, ITranslator) { // this class depend on all stuff which it need to send an email. } public void Handle(OrderCreated event) { this.mailer.Send(...); } } }

คลาส OrderCreated ถูกทำเครื่องหมายเป็น ต่อเนื่องได้ ยังไงซะ. เราสามารถจัดการเหตุการณ์นี้ได้นอกกรอบหรือจัดเก็บไว้เป็นลำดับในแถว (Redis, ActiveMQ หรือสิ่งอื่นใด) และประมวลผลในกระบวนการ / เธรดที่แยกจากกันไปยังเหตุการณ์ที่จัดการคำขอทางเว็บ ใน บทความนี้ ผู้เขียนอธิบายรายละเอียดว่าสถาปัตยกรรมที่ขับเคลื่อนด้วยเหตุการณ์คืออะไร (อย่าใส่ใจกับตรรกะทางธุรกิจภายใน OrderController ).

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

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

ข้อสรุป

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

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

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

การวิเคราะห์ผลกระทบของอัตราดอกเบี้ยติดลบในห้าเศรษฐกิจ

กระบวนการทางการเงิน

การวิเคราะห์ผลกระทบของอัตราดอกเบี้ยติดลบในห้าเศรษฐกิจ
Back End: การใช้ Gatsby.js และ Node.js สำหรับการอัปเดตไซต์แบบคงที่

Back End: การใช้ Gatsby.js และ Node.js สำหรับการอัปเดตไซต์แบบคงที่

ส่วนหลัง

โพสต์ยอดนิยม
พลังของปากกา: บทช่วยสอนการเขียนตัวอักษร
พลังของปากกา: บทช่วยสอนการเขียนตัวอักษร
Strength in Numbers - ภาพรวมของการออกแบบที่ขับเคลื่อนด้วยข้อมูล
Strength in Numbers - ภาพรวมของการออกแบบที่ขับเคลื่อนด้วยข้อมูล
คุณสมบัติสิบ Kotlin เพื่อเพิ่มการพัฒนา Android
คุณสมบัติสิบ Kotlin เพื่อเพิ่มการพัฒนา Android
ข้อมูลเบื้องต้นเกี่ยวกับ PHP 7: มีอะไรใหม่และมีอะไรบ้าง
ข้อมูลเบื้องต้นเกี่ยวกับ PHP 7: มีอะไรใหม่และมีอะไรบ้าง
ใช้ InVision's Craft เพื่อการทำงานร่วมกันในทีมที่คล่องตัว
ใช้ InVision's Craft เพื่อการทำงานร่วมกันในทีมที่คล่องตัว
 
ทำความคุ้นเคย - คำแนะนำเกี่ยวกับขั้นตอนการเริ่มต้นใช้งานของผู้ใช้
ทำความคุ้นเคย - คำแนะนำเกี่ยวกับขั้นตอนการเริ่มต้นใช้งานของผู้ใช้
คู่มือนักออกแบบผลิตภัณฑ์เกี่ยวกับการวิเคราะห์การแข่งขัน
คู่มือนักออกแบบผลิตภัณฑ์เกี่ยวกับการวิเคราะห์การแข่งขัน
5 ความหวังที่ผิดพลาดในการต่อสู้และวิธีแก้ไข
5 ความหวังที่ผิดพลาดในการต่อสู้และวิธีแก้ไข
ApeeScape's Selection Of Best Developer Blogs
ApeeScape's Selection Of Best Developer Blogs
ไข่มุกแห่งปัญญา - จดหมายของผู้ถือหุ้นที่ดีที่สุดที่ไม่มีใครอ่าน
ไข่มุกแห่งปัญญา - จดหมายของผู้ถือหุ้นที่ดีที่สุดที่ไม่มีใครอ่าน
โพสต์ยอดนิยม
  • ผลตอบแทนจากการลงทุน การวัดผลการปฏิบัติงาน:
  • การจัดทำงบประมาณเงินทุนและการวิเคราะห์การลงทุน
  • เว็บไซต์หาคู่ที่ใหญ่ที่สุดในสหรัฐอเมริกา
  • อัตรารายชั่วโมงของผู้รับเหมาต่อเงินเดือน
  • php แสดงข้อมูลโพสต์ทั้งหมด
หมวดหมู่
  • เทคโนโลยี
  • การจัดการวิศวกรรม
  • ผู้คนและทีมงาน
  • ส่วนหลัง
  • © 2022 | สงวนลิขสิทธิ์

    portaldacalheta.pt