Khi tôi viết mã, tôi luôn cố gắng làm cho mã của mình sạch và dễ đọc nhất có thể.
Thỉnh thoảng sẽ có lúc bạn cần vượt qua ranh giới và chuyển từ mã sạch đẹp sang mã hơi xấu hơn để làm cho nó nhanh hơn.
Khi nào thì vượt qua dòng đó?
Khi tôi viết mã, tôi luôn cố gắng làm cho mã của mình sạch và dễ đọc nhất có thể.
Thỉnh thoảng sẽ có lúc bạn cần vượt qua ranh giới và chuyển từ mã sạch đẹp sang mã hơi xấu hơn để làm cho nó nhanh hơn.
Khi nào thì vượt qua dòng đó?
Câu trả lời:
Bạn băng qua đường khi
Đây là một ví dụ thực tế: một hệ thống thử nghiệm tôi đang chạy đang tạo ra dữ liệu quá chậm, mất hơn 9 giờ mỗi lần chạy và chỉ sử dụng 40% CPU. Thay vì làm rối mã quá nhiều, tôi đã chuyển tất cả các tệp tạm thời sang một hệ thống tệp trong bộ nhớ. Đã thêm 8 dòng mã không xấu mới và hiện tại mức sử dụng CPU là trên 98%. Vấn đề được giải quyết; không có sự xấu xí cần thiết
foo
và đổi tên nó foo_ref
- thông thường nó tồn tại ngay trên foo
tệp nguồn. Trong khai thác thử nghiệm của tôi, tôi gọi foo
và foo_ref
để xác nhận và đo lường hiệu suất tương đối.
Đó là một sự phân đôi giả. Bạn có thể tạo mã nhanh và dễ bảo trì.
Cách bạn làm là viết nó sạch sẽ, đặc biệt là với cấu trúc dữ liệu càng đơn giản càng tốt.
Sau đó, bạn tìm ra nơi thoát thời gian (bằng cách chạy nó, sau khi bạn đã viết nó chứ không phải trước đó) và sửa từng cái một. (Đây là một ví dụ.)
Đã thêm: Chúng ta luôn nghe về sự đánh đổi, phải không, chẳng hạn như sự đánh đổi giữa thời gian và ký ức, hay sự đánh đổi giữa tốc độ và khả năng bảo trì? Mặc dù các đường cong như vậy có thể tồn tại tốt, không nên giả định rằng bất kỳ chương trình đã cho nào nằm trên đường cong hoặc thậm chí bất kỳ nơi nào gần nó.
Bất kỳ chương trình nào trên đường cong đều có thể dễ dàng (bằng cách đưa nó cho một loại lập trình viên nhất định) được thực hiện chậm hơn nhiều, và ít bảo trì hơn, và sau đó nó sẽ không ở đâu gần đường cong. Một chương trình như vậy sau đó có rất nhiều phòng để được thực hiện nhanh hơn và dễ bảo trì hơn.
Theo kinh nghiệm của tôi, đó là nơi nhiều chương trình bắt đầu.
Trong sự tồn tại OSS của tôi, tôi thực hiện rất nhiều công việc thư viện nhằm mục đích thực hiện, nó gắn chặt với cấu trúc dữ liệu của người gọi (tức là bên ngoài thư viện), với (theo thiết kế) không bắt buộc đối với các loại đến. Ở đây, cách tốt nhất để làm cho trình diễn này là lập trình meta, mà (vì tôi ở .NET-đất) có nghĩa là IL-emit. Đó là một số mã xấu xí, xấu xí, nhưng rất nhanh.
Theo cách này, tôi vui vẻ chấp nhận mã thư viện có thể "xấu" hơn mã ứng dụng , đơn giản là vì nó có ít quyền kiểm soát (hoặc có thể không) đối với các yếu tố đầu vào , do đó cần phải đạt được một số nhiệm vụ thông qua các cơ chế khác nhau. Hoặc như tôi đã bày tỏ vào một ngày khác:
"mã hóa trên vách đá điên rồ, vì vậy bạn không cần phải "
Bây giờ mã ứng dụng hơi khác một chút, vì đó là nơi các nhà phát triển "thông thường" (lành mạnh) thường đầu tư nhiều thời gian hợp tác / chuyên nghiệp của họ; mục tiêu và kỳ vọng của mỗi người là (IMO) hơi khác nhau.
IMO, các câu trả lời ở trên cho thấy nó có thể nhanh chóng và dễ bảo trì đang đề cập đến mã ứng dụng nơi nhà phát triển có quyền kiểm soát nhiều hơn đối với các cấu trúc dữ liệu và không sử dụng các công cụ như lập trình meta. Điều đó nói rằng, có nhiều cách khác nhau để lập trình meta, với mức độ điên rồ khác nhau và mức độ chi phí khác nhau. Ngay cả trong lĩnh vực đó, bạn cần chọn mức độ trừu tượng thích hợp. Nhưng khi bạn chủ động, tích cực, thực sự muốn nó xử lý dữ liệu bất ngờ theo cách nhanh nhất tuyệt đối; nó cũng có thể trở nên xấu xí Đối phó với nó; p
Khi bạn đã lược tả mã và xác minh rằng nó thực sự gây ra sự chậm chạp đáng kể.
Mã sạch không nhất thiết là độc quyền với mã thực thi nhanh. Thông thường mã khó đọc được viết vì nó viết nhanh hơn, không phải vì nó thực thi nhanh hơn.
Viết mã "bẩn" trong một nỗ lực để làm cho nó nhanh hơn được cho là không khôn ngoan, vì bạn không biết chắc chắn rằng những thay đổi của bạn thực sự cải thiện bất cứ điều gì. Knuth đặt nó tốt nhất:
"Chúng ta nên quên đi những hiệu quả nhỏ, nói về 97% thời gian: tối ưu hóa sớm là gốc rễ của mọi tội lỗi . Tuy nhiên, chúng ta không nên bỏ qua cơ hội của mình trong 3% quan trọng đó. Một lập trình viên giỏi sẽ không bị ruồng bỏ bởi sự tự mãn như vậy Lý do, anh ta sẽ khôn ngoan khi xem xét kỹ mã quan trọng; nhưng chỉ sau khi mã đó được xác định . "
Nói cách khác, viết mã sạch trước. Sau đó, lập hồ sơ chương trình kết quả và xem liệu phân đoạn đó trên thực tế có phải là nút cổ chai hiệu năng không. Nếu vậy, hãy tối ưu hóa phần này khi cần thiết và đảm bảo bao gồm nhiều ý kiến tài liệu (có thể bao gồm cả mã gốc) để giải thích các tối ưu hóa. Sau đó hồ sơ kết quả để xác minh rằng bạn thực sự đã thực hiện một cải tiến.
Vì câu hỏi nói "nhanh khó đọc mã", nên câu trả lời đơn giản là không bao giờ. Không bao giờ có một lý do để viết mã khó đọc. Tại sao? Hai lý do.
Khi đó là mã vứt đi. Ý tôi là theo nghĩa đen: khi bạn viết một kịch bản để thực hiện phép tính hoặc tác vụ một lần và biết chắc chắn như vậy, bạn sẽ không bao giờ phải thực hiện lại hành động đó mà bạn có thể 'rm nguồn-tệp' mà không do dự, sau đó bạn có thể chọn con đường xấu xí.
Mặt khác, đó là một sự phân đôi giả - nếu bạn nghĩ rằng bạn cần làm cho nó xấu đi để làm nó nhanh hơn, thì bạn đã làm sai. (Hoặc nguyên tắc của bạn về những gì mã tốt cần sửa đổi. Sử dụng goto trên thực tế khá thanh lịch khi đó là giải pháp phù hợp cho vấn đề. Tuy nhiên, điều này hiếm khi xảy ra.)
Bất cứ khi nào chi phí ước tính của hiệu suất thấp hơn trên thị trường lớn hơn chi phí bảo trì mã ước tính cho mô-đun mã được đề cập.
Mọi người vẫn làm SSE / NEON / vân vân xoắn. lắp ráp để thử và đánh bại một số phần mềm của đối thủ cạnh tranh trên chip CPU phổ biến năm nay.
Đừng quên bạn có thể làm cho mã khó đọc trở nên dễ hiểu bằng tài liệu và nhận xét thích hợp.
Nói chung, hồ sơ sau khi bạn đã viết mã dễ đọc thực hiện chức năng mong muốn. Nút cổ chai có thể yêu cầu bạn làm điều gì đó khiến nó trông phức tạp hơn, nhưng bạn khắc phục điều đó bằng cách giải thích chính mình.
Đối với tôi đó là một tỷ lệ ổn định (như trong xi măng trong bê tông, đất sét nung trong lò, đặt trong đá, viết bằng mực vĩnh viễn). Mã của bạn càng không ổn định, vì càng có xác suất cao bạn sẽ cần thay đổi nó trong tương lai, thì nó càng dễ dàng trở nên dễ dàng hơn, như đất sét ướt, để duy trì hiệu quả. Tôi cũng nhấn mạnh đến tính dễ đọc và không dễ đọc. Đối với tôi sự dễ dàng thay đổi mã quan trọng hơn sự dễ đọc của nó. Mã có thể dễ đọc và là cơn ác mộng để thay đổi, và cách sử dụng nào có thể đọc và dễ dàng hiểu chi tiết thực hiện nếu chúng là cơn ác mộng để thay đổi? Trừ khi nó chỉ là một bài tập học thuật, điển hình là việc có thể dễ dàng hiểu được mã trong một cơ sở mã sản xuất với mục đích có thể dễ dàng thay đổi nó hơn khi cần. Nếu khó thay đổi, sau đó rất nhiều lợi ích của khả năng đọc đi ra ngoài cửa sổ. Khả năng đọc nói chung chỉ hữu ích trong bối cảnh của tính dễ uốn, và tính dễ uốn chỉ hữu ích trong bối cảnh không ổn định.
Đương nhiên, ngay cả những mã khó bảo trì nhất có thể tưởng tượng được, bất kể nó dễ đọc hay khó, không gây ra vấn đề gì nếu không bao giờ có lý do để thay đổi mã, chỉ sử dụng nó. Và có thể đạt được chất lượng như vậy, đặc biệt đối với mã hệ thống cấp thấp, nơi hiệu suất thường có xu hướng được tính nhiều nhất. Tôi có mã C tôi vẫn sử dụng thường xuyên đã không thay đổi kể từ cuối những năm 80. Nó không cần phải thay đổi kể từ đó. Các mã được đào tẩu, được viết trong những ngày khó khăn, và tôi hầu như không hiểu nó. Tuy nhiên, ngày nay nó vẫn được áp dụng và tôi không cần phải hiểu cách triển khai của nó để có được nhiều công dụng từ nó.
Kiểm tra kỹ lưỡng bằng văn bản là một cách để cải thiện sự ổn định. Một cái khác là tách rời. Nếu mã của bạn không phụ thuộc vào bất cứ điều gì khác, thì lý do duy nhất để nó thay đổi là nếu chính nó, cần phải thay đổi. Đôi khi, một số lượng nhỏ mã sao chép có thể đóng vai trò là cơ chế tách rời để cải thiện đáng kể tính ổn định theo cách làm cho nó trở thành một sự đánh đổi xứng đáng nếu đổi lại, bạn có được mã hoàn toàn độc lập với bất kỳ thứ gì khác. Bây giờ mã đó là bất khả xâm phạm để thay đổi với thế giới bên ngoài. Trong khi đó, mã phụ thuộc vào 10 thư viện bên ngoài khác nhau có 10 lần lý do để nó thay đổi trong tương lai.
Một điều hữu ích khác trong thực tế là tách thư viện của bạn khỏi các phần không ổn định của cơ sở mã của bạn, thậm chí có thể xây dựng nó một cách riêng biệt, như bạn có thể làm cho các thư viện bên thứ ba (tương tự như vậy chỉ được sử dụng, không thay đổi, ít nhất là không phải bởi đội). Chỉ loại hình tổ chức đó có thể ngăn chặn mọi người can thiệp vào nó.
Khác là chủ nghĩa tối giản. Mã của bạn càng ít cố gắng làm, càng có nhiều khả năng nó có thể làm những gì nó làm tốt. Các thiết kế nguyên khối gần như không ổn định vĩnh viễn, vì càng nhiều chức năng được thêm vào chúng, chúng dường như càng không hoàn thiện.
Tính ổn định phải là mục tiêu chính của bạn bất cứ khi nào bạn đặt mục tiêu viết mã chắc chắn sẽ khó thay đổi, như mã SIMD song song đã được điều chỉnh vi mô đến chết. Bạn chống lại khó khăn trong việc duy trì mã bằng cách tối đa hóa khả năng bạn sẽ không phải thay đổi mã, và do đó sẽ không phải duy trì mã trong tương lai. Điều đó làm giảm chi phí bảo trì xuống 0 cho dù mã có khó bảo trì đến đâu.