Có thể thay thế mã tối ưu hóa bằng mã có thể đọc được không?


78

Đôi khi bạn gặp phải tình huống phải mở rộng / cải thiện một số mã hiện có. Bạn thấy rằng mã cũ rất nạc, nhưng nó cũng khó mở rộng và mất thời gian để đọc.

Có phải là một ý tưởng tốt để thay thế nó bằng mã hiện đại?

Trước đây, tôi thích cách tiếp cận tinh gọn, nhưng bây giờ, có vẻ như tôi tốt hơn là hy sinh rất nhiều tối ưu hóa để có tính trừu tượng cao hơn, giao diện tốt hơn và mã mở rộng hơn, dễ đọc hơn.

Các trình biên dịch dường như cũng đang trở nên tốt hơn, vì vậy những thứ như struct abc = {}âm thầm biến thành memsets, shared_ptrs tạo ra khá nhiều mã giống như vặn con trỏ thô, các mẫu đang hoạt động siêu tốt vì chúng tạo ra mã siêu nạc, v.v.

Tuy nhiên, đôi khi bạn thấy các mảng dựa trên ngăn xếp và các hàm C cũ với một số logic tối nghĩa và thông thường chúng không nằm trên đường dẫn quan trọng.

Có phải là một ý tưởng tốt để thay đổi mã như vậy nếu bạn phải chạm vào một phần nhỏ của nó theo cách nào?


20
Khả năng đọc và tối ưu hóa không bị phản đối hầu hết thời gian.
deadalnix

23
Khả năng đọc có thể cải thiện với một số ý kiến?
YetAntherUser

17
Thật đáng lo ngại khi OOP-ification được coi là 'mã hiện đại'
James

7
giống như triết lý slackware: nếu nó không bị hỏng thì đừng sửa nó, ít nhất bạn có một lý do rất, rất tốt để làm điều đó
osdamv

5
Theo mã được tối ưu hóa, bạn có nghĩa là mã được tối ưu hóa thực tế , hay còn gọi làđược tối ưu hóa?
dan04

Câu trả lời:


115

Ở đâu?

  • Trên trang chủ của một trang web quy mô Google, không được chấp nhận. Giữ mọi thứ càng nhanh càng tốt.

  • Trong một phần của ứng dụng được sử dụng bởi một người mỗi năm một lần, việc hy sinh hiệu năng để có được khả năng đọc mã là hoàn toàn chấp nhận được.

Nói chung, các yêu cầu phi chức năng đối với phần mã bạn đang làm việc là gì? Nếu một hành động phải thực hiện dưới 900 ms. trong một bối cảnh nhất định (máy, tải, v.v.) 80% thời gian và thực tế, nó thực hiện dưới 200 ms. 100% thời gian, chắc chắn, làm cho mã dễ đọc hơn ngay cả khi nó có thể ảnh hưởng một chút đến hiệu suất. Mặt khác, nếu hành động tương tự không bao giờ được thực hiện dưới mười giây, tốt hơn là bạn nên thử xem có gì sai với hiệu suất (hoặc yêu cầu ở vị trí đầu tiên).

Ngoài ra, làm thế nào cải thiện khả năng đọc sẽ làm giảm hiệu suất? Thông thường, các nhà phát triển đang điều chỉnh hành vi gần với tối ưu hóa sớm: họ sợ tăng khả năng đọc, tin rằng nó sẽ phá hủy mạnh mẽ hiệu suất, trong khi mã dễ đọc hơn sẽ tốn thêm vài micrô giây để thực hiện cùng một hành động.


47
+1! Nếu bạn không có số, hãy lấy một số. Nếu bạn không có thời gian để lấy số, bạn không có thời gian để thay đổi nó.
Tacroy

49
Chẳng hạn, các nhà phát triển thường "tối ưu hóa" dựa trên huyền thoại và hiểu lầm, bằng cách giả sử rằng "C" nhanh hơn "C ++" và tránh các tính năng của C ++ ra khỏi cảm giác chung rằng mọi thứ nhanh hơn mà không cần số để sao lưu. Nhắc nhở tôi về một nhà phát triển C tôi đã theo dõi, người nghĩ rằng gotonhanh hơn các vòng lặp. Trớ trêu thay, trình tối ưu hóa đã làm tốt hơn với các vòng lặp, vì vậy anh ta làm cho mã chậm hơn khó đọc hơn.
Steven Burnap

6
Thay vì thêm câu trả lời khác, tôi + 1'd câu trả lời này. Nếu hiểu các đoạn mã là quan trọng, hãy nhận xét chúng tốt. Tôi đã làm việc trong môi trường C / C ++ / hội với mã kế thừa một thập kỷ với hàng chục người đóng góp. Nếu mã hoạt động, hãy để nó một mình và trở lại làm việc.
Chris K

Đó là lý do tại sao tôi có xu hướng chỉ viết mã có thể đọc được. Hiệu suất có thể đạt được để cắt giảm một vài điểm nóng.
Luca

36

Thông thường, không .

Thay đổi mã có thể gây ra các sự cố không lường trước được ở những nơi khác trong hệ thống (đôi khi có thể không được chú ý cho đến sau này trong một dự án nếu bạn không có đơn vị kiểm tra và kiểm tra khói tại chỗ). Tôi thường đi theo "nếu nó không bị hỏng, đừng sửa nó".

Ngoại lệ cho quy tắc này là nếu bạn đang triển khai một tính năng mới chạm vào mã này. Nếu tại thời điểm đó, nó không có ý nghĩa và tái cấu trúc thực sự cần phải diễn ra, thì hãy duy trì nó miễn là thời gian tái cấu trúc (và đủ kiểm tra và bộ đệm để xử lý các vấn đề gõ cửa) đều được tính toán trong các ước tính.

Tất nhiên, hồ sơ, hồ sơ, hồ sơ , đặc biệt nếu đó là một khu vực đường dẫn quan trọng.


2
Có, nhưng bạn cho rằng tối ưu hóa là cần thiết. Chúng tôi không biết luôn luôn biết nếu có, và có lẽ chúng tôi muốn xác định điều này trước tiên.
haylem

2
@haylem: Không, tôi giả sử rằng mã hoạt động như vốn có. Tôi cũng cho rằng việc tái cấu trúc mã sẽ luôn gây ra các sự cố gõ cửa ở nơi khác trong hệ thống (trừ khi bạn đang xử lý một đoạn mã không quan trọng mà không có bất kỳ phụ thuộc bên ngoài nào).
Demian Brecht

Có một số sự thật trong câu trả lời này, và trớ trêu thay điều này là do các vấn đề gõ cửa hiếm khi được ghi lại, hiểu, truyền đạt hoặc thậm chí được các nhà phát triển chú ý. Nếu các nhà phát triển hiểu sâu hơn về các vấn đề đã xảy ra trong quá khứ, họ sẽ biết phải đo lường cái gì và sẽ tự tin hơn khi thực hiện thay đổi mã.
rwong

29

Tóm lại: nó phụ thuộc

  • Bạn có thực sự cần hoặc sử dụng phiên bản tái cấu trúc / nâng cao của bạn?

    • Có một lợi ích cụ thể, ngay lập tức hoặc lâu dài?
    • Là lợi ích này chỉ cho khả năng bảo trì, hoặc thực sự kiến ​​trúc?
  • Có thực sự cần phải được tối ưu hóa?

    • Tại sao?
    • Mục tiêu bạn cần đạt được là gì?

Chi tiết

Bạn sẽ cần những thứ sạch sẽ, sáng bóng?

Có những điều cần thận trọng ở đây, và bạn cần xác định giới hạn giữa mức tăng thực, mức có thể đo được và đâu là sở thích cá nhân của bạn và thói quen xấu tiềm ẩn khi chạm vào mã không nên có.

Cụ thể hơn, biết điều này:

Có một thứ như Over-Engineering

Đó là một mô hình chống, và nó đi kèm với các vấn đề tích hợp:

  • có thể mở rộng hơn , nhưng có thể không dễ dàng mở rộng hơn,
  • có thể không đơn giản để hiểu ,
  • cuối cùng, nhưng chắc chắn không kém phần quan trọng ở đây: bạn có thể làm chậm toàn bộ mã.

Một số người cũng có thể đề cập đến nguyên tắc KISS như một tài liệu tham khảo, nhưng ở đây nó phản trực giác: cách tối ưu hóa theo cách đơn giản hay cách kiến ​​trúc sạch sẽ? Câu trả lời không nhất thiết là tuyệt đối, như được giải thích trong phần còn lại dưới đây.

Bạn không cần nó

Các nguyên tắc YAGNI là không hoàn toàn trực giao với các vấn đề khác, nhưng nó giúp để tự hỏi mình câu hỏi: Bạn sẽ cần đến nó?

Liệu kiến ​​trúc phức tạp hơn có thực sự mang lại lợi ích cho bạn, ngoài việc mang lại vẻ ngoài dễ bảo trì hơn?

Nếu nó không bị hỏng, đừng sửa nó

Viết cái này lên một tấm áp phích lớn và treo nó bên cạnh màn hình của bạn hoặc trong khu vực nhà bếp tại nơi làm việc, hoặc trong phòng họp của nhà phát triển. Tất nhiên có rất nhiều câu thần chú khác đáng để bạn lặp lại, nhưng câu thần chú này rất quan trọng mỗi khi bạn cố gắng thực hiện "công việc bảo trì" và cảm thấy thôi thúc "cải thiện" nó.

Thật tự nhiên khi chúng ta muốn "cải thiện" mã hoặc thậm chí chỉ cần chạm vào nó, thậm chí vô thức, khi chúng ta đọc qua nó để cố gắng hiểu nó. Đó là một điều tốt, vì điều đó có nghĩa là chúng tôi quan tâm và cố gắng hiểu sâu hơn về nội bộ, nhưng nó cũng bị ràng buộc với trình độ kỹ năng, kiến ​​thức của chúng tôi (làm thế nào để bạn quyết định điều gì tốt hơn hay không? ...) và tất cả các giả định chúng tôi đưa ra về những gì chúng tôi nghĩ rằng chúng tôi biết phần mềm ...:

  • thực sự làm
  • thực sự cần phải làm
  • cuối cùng sẽ cần phải làm,
  • và nó làm tốt như thế nào

Có thực sự cần phải được tối ưu hóa?

Tất cả điều này nói, tại sao nó được "tối ưu hóa" ngay từ đầu? Họ nói rằng tối ưu hóa sớm là gốc rễ của mọi tội lỗi và nếu bạn thấy mã không có giấy tờ và dường như được tối ưu hóa, thông thường bạn có thể cho rằng nó có thể không tuân theo Quy tắc Tối ưu hóa không cần nỗ lực tối ưu hóa và đó chỉ là Sự kiêu ngạo của nhà phát triển thông thường đang khởi động. Một lần nữa, có lẽ bây giờ chỉ là của bạn.

Nếu có, trong giới hạn nào nó trở nên chấp nhận được? Nếu có nhu cầu về nó, giới hạn này tồn tại và cung cấp cho bạn cơ hội để cải thiện mọi thứ hoặc khó khăn để quyết định để nó đi.

Ngoài ra, hãy cẩn thận với các đặc điểm vô hình. Rất có thể, phiên bản "có thể mở rộng" của mã này sẽ giúp bạn tăng thêm bộ nhớ trong thời gian chạy, và thậm chí còn hiển thị cả bộ nhớ tĩnh lớn hơn cho tệp thực thi. Các tính năng OO sáng bóng đi kèm với chi phí không trực quan như thế này và chúng có thể quan trọng đối với chương trình của bạn và môi trường mà nó phải chạy.

Đo lường, đo lường, đo lường

Như Google hiện tại, tất cả là về dữ liệu! Nếu bạn có thể sao lưu dữ liệu, thì cần thiết.

Có một câu chuyện không quá cũ đến nỗi cứ 1 đô la chi cho phát triển, nó sẽ được theo sau ít nhất 1 đô la để thử nghiệm và ít nhất là 1 đô la hỗ trợ (nhưng thực sự, nó còn nhiều hơn thế).

Thay đổi tác động rất nhiều thứ:

  • bạn có thể cần phải tạo một bản dựng mới;
  • bạn nên viết các bài kiểm tra đơn vị mới (chắc chắn nếu không có bài kiểm tra nào, và kiến ​​trúc mở rộng hơn của bạn có thể còn nhiều chỗ hơn, vì bạn có nhiều bề mặt hơn cho các lỗi);
  • bạn nên viết các bài kiểm tra hiệu suất mới (để đảm bảo rằng điều này sẽ ổn định trong tương lai và để xem các nút thắt cổ chai ở đâu), và những điều này rất khó thực hiện ;
  • bạn sẽ cần ghi lại nó (và mở rộng hơn có nghĩa là có nhiều chỗ hơn để biết chi tiết);
  • bạn (hoặc người khác) sẽ cần kiểm tra lại rộng rãi trong QA;
  • mã (hầu như) không bao giờ không có lỗi và bạn sẽ cần hỗ trợ nó.

Vì vậy, nó không chỉ tiêu thụ tài nguyên phần cứng (tốc độ thực hiện hoặc dấu chân bộ nhớ) mà bạn cần đo ở đây, mà còn là mức tiêu thụ tài nguyên nhóm . Cả hai cần phải được dự đoán để xác định mục tiêu mục tiêu, được đo lường, tính toán và điều chỉnh dựa trên sự phát triển.

Và đối với người quản lý của bạn, điều đó có nghĩa là phù hợp với kế hoạch phát triển hiện tại, do đó, hãy liên lạc về nó và đừng tham gia vào mã hóa con bò / tàu ngầm / tàu ngầm đen.


Nói chung...

Đúng nhưng...

Nói chung, đừng hiểu sai ý tôi, tôi muốn ủng hộ lý do tại sao bạn đề xuất và tôi thường ủng hộ điều đó. Nhưng bạn cần lưu ý về chi phí dài hạn.

Trong một thế giới hoàn hảo, đó là giải pháp phù hợp:

  • phần cứng máy tính trở nên tốt hơn theo thời gian,
  • trình biên dịch và nền tảng thời gian chạy trở nên tốt hơn theo thời gian,
  • bạn có được mã gần hoàn hảo, sạch sẽ, có thể bảo trì và dễ đọc.

Trong thực tế:

  • bạn có thể làm cho nó tồi tệ hơn

    Bạn cần nhiều nhãn cầu hơn để nhìn vào nó, và bạn càng phức tạp hóa nó, bạn càng cần nhiều nhãn cầu hơn.

  • bạn không thể dự đoán tương lai

    Bạn không thể biết chắc chắn tuyệt đối nếu bạn cần nó và ngay cả khi "tiện ích mở rộng" bạn cần sẽ dễ dàng và nhanh chóng hơn để thực hiện ở dạng cũ và nếu bản thân chúng cần phải được tối ưu hóa siêu hạng .

  • nó đại diện, từ quan điểm của quản lý, một chi phí lớn cho không có lợi ích trực tiếp.

Biến nó thành một phần của quá trình

Bạn đề cập ở đây rằng đó là một thay đổi khá nhỏ và bạn có một số vấn đề cụ thể trong đầu. Tôi muốn nói rằng nó thường ổn trong trường hợp này, nhưng hầu hết chúng ta cũng có những câu chuyện cá nhân về những thay đổi nhỏ, những chỉnh sửa gần như phẫu thuật, cuối cùng biến thành cơn ác mộng bảo trì và thời hạn gần như bị bỏ lỡ hoặc bùng nổ vì Joe Lập trình viên không thấy về những lý do đằng sau mã và chạm vào thứ gì đó không nên có.

Nếu bạn có một quy trình để xử lý các quyết định đó, bạn loại bỏ các khía cạnh cá nhân của chúng:

  • Nếu bạn kiểm tra mọi thứ một cách chính xác, bạn sẽ biết nhanh hơn nếu mọi thứ bị hỏng,
  • Nếu bạn đo lường chúng, bạn sẽ biết nếu chúng được cải thiện,
  • Nếu bạn xem xét nó, bạn sẽ biết nếu nó ném mọi người đi.

Kiểm tra phạm vi, hồ sơ và thu thập dữ liệu là khó khăn

Nhưng, tất nhiên, mã kiểm tra và số liệu của bạn có thể gặp phải cùng một vấn đề mà bạn đang cố tránh cho mã thực tế của mình: bạn có kiểm tra đúng thứ không và chúng có phải là thứ phù hợp cho tương lai không và bạn có đo lường đúng không nhiều thứ?

Tuy nhiên, nói chung, bạn càng kiểm tra (cho đến một giới hạn nhất định) và đo lường, bạn càng thu thập được nhiều dữ liệu và bạn càng an toàn. Thời gian tương tự tồi tệ: nghĩ về nó như lái xe (hoặc cuộc sống nói chung): bạn có thể là người lái xe tốt nhất trên thế giới, nếu chiếc xe bị hỏng hoặc bạn quyết định tự sát bằng xe của mình ngay hôm nay, kỹ năng có thể không đủ. Có cả những thứ môi trường có thể đánh vào bạn, và lỗi của con người cũng có vấn đề.

Nhận xét mã là thử nghiệm hành lang của nhóm phát triển

Và tôi nghĩ phần cuối cùng là chìa khóa ở đây: đánh giá mã. Bạn sẽ không biết giá trị của những cải tiến nếu bạn thực hiện chúng một mình. Đánh giá mã là "thử nghiệm hành lang" của chúng tôi: tuân theo phiên bản Luật Linus của Raymond để phát hiện lỗi và phát hiện kỹ thuật quá mức và các mô hình chống khác, và để đảm bảo rằng mã phù hợp với khả năng của nhóm bạn. Không có điểm nào có mã "tốt nhất" nếu không ai khác ngoài bạn có thể hiểu và duy trì nó, và điều đó phù hợp với cả tối ưu hóa mật mã và thiết kế kiến ​​trúc sâu 6 lớp.

Khi kết thúc, hãy nhớ:

Mọi người đều biết rằng gỡ lỗi khó gấp đôi so với viết chương trình ở nơi đầu tiên. Vì vậy, nếu bạn thông minh như bạn có thể khi bạn viết nó, làm thế nào bạn sẽ gỡ lỗi nó? - Brian Kernighan


"Nếu nó không bị hỏng, đừng sửa nó" đi ngược lại việc tái cấu trúc. Không có vấn đề gì nếu một cái gì đó hoạt động, nếu không thể xác định được, cần phải được thay đổi.
Miyamoto Akira

@MiyamotoAkira: đó là một thứ hai tốc độ. Nếu nó không bị hỏng nhưng có thể chấp nhận và ít có khả năng thấy hỗ trợ, thể chấp nhận để nó một mình thay vì đưa ra các lỗi mới tiềm ẩn hoặc dành thời gian phát triển cho nó. Đó là tất cả về việc đánh giá lợi ích, cả ngắn hạn và dài hạn, của tái cấu trúc. Không có câu trả lời rõ ràng, nó đòi hỏi một số đánh giá.
haylem

đã đồng ý. Tôi cho rằng tôi không thích câu (và triết lý đằng sau nó) bởi vì tôi thấy tái cấu trúc là tùy chọn mặc định và chỉ khi nó dường như sẽ mất quá nhiều thời gian, hoặc quá khó thì nó sẽ / nên được quyết định không đi với nó. Xin lưu ý bạn, tôi đã bị đốt cháy bởi những người không thay đổi mọi thứ, rằng mặc dù đang hoạt động, rõ ràng là giải pháp sai ngay khi bạn phải duy trì chúng hoặc mở rộng chúng.
Miyamoto Akira

@MiyamotoAkira: câu ngắn và phát biểu ý kiến ​​không thể diễn tả nhiều. Chúng có nghĩa là ở trong khuôn mặt của bạn, và được phát triển ở một bên, tôi đoán vậy. Bản thân tôi rất quan trọng trong việc xem xét và chạm vào mã thường xuyên nhất có thể, ngay cả khi thường xuyên không có mạng lưới an toàn lớn hoặc không có nhiều lý do. Nếu nó bẩn, bạn làm sạch nó. Nhưng, tương tự, tôi cũng bị bỏng vài lần. Và vẫn sẽ bị đốt cháy. Miễn là nó không phải là độ 3, tôi không bận tâm lắm, cho đến nay nó luôn là những vết bỏng ngắn hạn để đạt được lợi ích lâu dài.
haylem

8

Nói chung, bạn nên tập trung vào khả năng đọc trước và hiệu suất cao hơn sau đó. Hầu hết thời gian, những tối ưu hóa hiệu suất dù sao cũng không đáng kể, nhưng chi phí bảo trì có thể rất lớn.

Chắc chắn tất cả những điều "nhỏ" nên được thay đổi theo hướng rõ ràng vì như bạn đã chỉ ra, hầu hết chúng sẽ được tối ưu hóa bởi trình biên dịch.

Đối với các tối ưu hóa lớn hơn, có thể có khả năng tối ưu hóa thực sự quan trọng để đạt được hiệu suất hợp lý (mặc dù điều này không thường xuyên xảy ra một cách đáng ngạc nhiên). Tôi sẽ thực hiện các thay đổi của bạn và sau đó hồ sơ mã trước và sau khi thay đổi. Nếu mã mới có vấn đề về hiệu năng đáng kể, bạn luôn có thể quay lại phiên bản được tối ưu hóa và nếu không, bạn chỉ có thể sử dụng phiên bản mã sạch hơn.

Chỉ thay đổi một phần mã tại một thời điểm và xem nó ảnh hưởng đến hiệu suất như thế nào sau mỗi vòng tái cấu trúc.


8

Nó phụ thuộc vào lý do tại sao mã được tối ưu hóa và tác động của việc thay đổi nó sẽ là gì và tác động của mã đến hiệu suất tổng thể có thể là gì. Nó cũng phụ thuộc vào việc bạn có cách tốt để tải các thay đổi kiểm tra hay không.

Bạn không nên thực hiện thay đổi này mà không định hình trước và sau và chắc chắn dưới một tải tương tự như những gì sẽ thấy trong sản xuất. Điều đó có nghĩa là không sử dụng một tập hợp con dữ liệu nhỏ trên máy của nhà phát triển hoặc thử nghiệm khi chỉ có một người dùng đang sử dụng hệ thống.

Nếu tối ưu hóa gần đây, bạn có thể nói chuyện với nhà phát triển và tìm hiểu chính xác vấn đề là gì và ứng dụng chậm như thế nào trước khi tối ưu hóa. Điều này có thể cho bạn biết rất nhiều về việc có nên thực hiện tối ưu hóa hay không và điều kiện tối ưu hóa là gì (ví dụ, báo cáo bao gồm cả năm có thể không trở nên chậm cho đến tháng 9 hoặc tháng 10, nếu bạn đang kiểm tra thay đổi của mình vào tháng Hai, sự chậm chạp có thể chưa rõ ràng và thử nghiệm không hợp lệ).

Nếu tối ưu hóa khá cũ, các phương thức mới hơn thậm chí có thể nhanh hơn cũng như dễ đọc hơn.

Cuối cùng, đây là một câu hỏi cho ông chủ của bạn. Thật tốn thời gian để cấu trúc lại một cái gì đó đã được tối ưu hóa và để đảm bảo rằng sự thay đổi không ảnh hưởng đến kết quả cuối cùng và nó thực hiện tốt hoặc ít nhất là chấp nhận được so với cách cũ. Anh ta có thể muốn bạn dành thời gian của mình trong các lĩnh vực khác thay vì thực hiện một nhiệm vụ rủi ro cao để tiết kiệm một vài phút thời gian mã hóa. Hoặc anh ta có thể đồng ý rằng mã này rất khó hiểu và cần được can thiệp thường xuyên và các phương pháp tốt hơn hiện đã có sẵn.


6

nếu hồ sơ cho thấy việc tối ưu hóa là không cần thiết (nó không nằm trong phần quan trọng) hoặc thậm chí có thời gian chạy kém hơn (do kết quả của tối ưu hóa sớm không tốt) thì chắc chắn thay thế bằng mã dễ đọc hơn để duy trì

cũng đảm bảo mã hoạt động giống với các thử nghiệm thích hợp


5

Hãy nghĩ về nó từ một quan điểm kinh doanh. Các chi phí của sự thay đổi là gì? Bạn cần bao nhiêu thời gian để thực hiện thay đổi và bạn sẽ tiết kiệm được bao nhiêu trong thời gian dài bằng cách làm cho mã dễ dàng mở rộng hoặc duy trì hơn? Bây giờ đính kèm một thẻ giá vào thời điểm đó và so sánh nó với số tiền bị mất bằng cách giảm hiệu suất. Có lẽ bạn cần thêm hoặc nâng cấp máy chủ để bù cho hiệu năng bị mất. Có thể sản phẩm không còn đáp ứng yêu cầu và không thể bán được nữa. Có lẽ không có mất mát. Có thể sự thay đổi làm tăng sự mạnh mẽ và tiết kiệm thời gian ở nơi khác. Bây giờ đưa ra quyết định của bạn.

Mặt khác, trong một số trường hợp, có thể giữ cả hai phiên bản của một đoạn. Bạn có thể viết thử nghiệm tạo các giá trị đầu vào ngẫu nhiên và xác minh kết quả với phiên bản khác. Sử dụng giải pháp "thông minh" để kiểm tra kết quả của một giải pháp hoàn toàn dễ hiểu và rõ ràng chính xác và từ đó đạt được một số đảm bảo (nhưng không có bằng chứng) rằng giải pháp mới tương đương với giải pháp cũ. Hoặc đi ngược lại và kiểm tra kết quả của mã phức tạp với mã dài dòng và từ đó ghi lại ý định đằng sau vụ hack một cách rõ ràng.


4

Về cơ bản, bạn đang hỏi liệu tái cấu trúc có phải là một liên doanh đáng giá hay không. Câu trả lời cho điều này chắc chắn là có.

Nhưng...

... Bạn cần phải làm điều đó một cách cẩn thận. Bạn cần kiểm tra đơn vị, tích hợp, chức năng và hiệu năng vững chắc cho bất kỳ mã nào bạn đang tái cấu trúc. Bạn cần tự tin rằng họ thực sự kiểm tra tất cả các chức năng cần thiết. Bạn cần khả năng để chạy chúng dễ dàng và lặp đi lặp lại. Khi bạn đã có điều đó, bạn sẽ có thể thay thế các thành phần bằng các thành phần mới có chức năng tương đương.

Martin Fowler đã viết cuốn sách về điều này.


3

Bạn không nên thay đổi làm việc, mã sản xuất mà không có lý do chính đáng. "Tái cấu trúc" không phải là một lý do đủ tốt trừ khi bạn không thể thực hiện công việc của mình mà không cần tái cấu trúc. Ngay cả khi những gì bạn đang làm là sửa các lỗi trong chính mã khó, bạn vẫn nên dành thời gian để hiểu nó và thực hiện thay đổi nhỏ nhất có thể. Nếu mã khó hiểu, bạn sẽ không thể hiểu nó hoàn toàn, và do đó, bất kỳ thay đổi nào bạn thực hiện sẽ có tác dụng phụ không thể đoán trước - các lỗi, nói cách khác. Thay đổi càng lớn, bạn càng có khả năng gây rắc rối.

Sẽ có một ngoại lệ cho điều này: nếu mã không thể hiểu được có một bộ kiểm tra đơn vị hoàn chỉnh, bạn có thể cấu trúc lại nó. Vì tôi chưa bao giờ thấy hoặc nghe thấy mã không thể hiểu được với các bài kiểm tra đơn vị hoàn chỉnh, trước tiên bạn viết bài kiểm tra đơn vị, có được sự đồng ý của những người cần thiết mà các bài kiểm tra đơn vị đó thực hiện đại diện cho những gì mã nên làm và THEN làm cho mã thay đổi . Tôi đã làm điều đó một hoặc hai lần; đó là một cơn đau ở cổ, và rất tốn kém, nhưng cuối cùng lại mang lại kết quả tốt.


3

Nếu nó chỉ là một đoạn mã ngắn làm một việc tương đối đơn giản theo cách khó hiểu, thì tôi sẽ chuyển "hiểu nhanh" trong một nhận xét mở rộng và / hoặc một triển khai thay thế không sử dụng, như

#ifdef READABLE_ALT_IMPLEMENTATION

   double x=0;
   for(double n: summands)
     x += n;
   return x;

#else

   auto subsum = [&](int lb, int rb){
          double x=0;
          while(lb<rb)
            x += summands[lb++];
          return x;
        };
   double x_fin=0;
   for(double nsm: par_eval( subsum
                           , partitions(n_threads, 0, summands.size()) ) )
     x_fin += nsm;
   return x_fin;

#endif

3

Câu trả lời là, không mất tính tổng quát, vâng. Luôn luôn thêm mã hiện đại khi bạn thấy khó đọc mã và xóa mã xấu trong hầu hết các trường hợp. Tôi sử dụng quy trình sau:

  1. Tìm kiếm các bài kiểm tra hiệu suất và hỗ trợ thông tin hồ sơ. Nếu không có kiểm tra hiệu suất, thì những gì có thể được khẳng định mà không có bằng chứng có thể được bác bỏ mà không có bằng chứng. Khẳng định rằng mã hiện đại của bạn nhanh hơn và loại bỏ mã cũ. Nếu bất cứ ai tranh luận (ngay cả chính bạn) yêu cầu họ viết mã hồ sơ để chứng minh cái nào nhanh hơn.
  2. Nếu mã hồ sơ tồn tại, hãy viết mã hiện đại. Đặt tên cho nó một cái gì đó như <function>_clean(). Sau đó, "chạy đua" mã của bạn với mã xấu. Nếu mã của bạn tốt hơn, hãy xóa mã cũ.
  3. Nếu mã cũ nhanh hơn, hãy để mã hiện đại của bạn ở đó. Nó phục vụ như một tài liệu tốt cho những gì mã khác có nghĩa là để làm và vì mã "chủng tộc" ở đó, bạn có thể tiếp tục chạy nó để ghi lại các đặc điểm hiệu suất và sự khác biệt giữa hai đường dẫn. Bạn cũng có thể kiểm tra đơn vị cho sự khác biệt trong hành vi mã. Điều quan trọng, mã hiện đại sẽ đánh bại mã "được tối ưu hóa" một ngày, đảm bảo. Sau đó bạn có thể loại bỏ mã xấu.

QED.


3

Nếu tôi có thể dạy thế giới một điều (về Phần mềm) trước khi chết, tôi sẽ dạy rằng "Hiệu suất so với X" là một vấn đề nan giải giả.

Tái cấu trúc thường được biết đến như một lợi ích cho khả năng đọc và độ tin cậy, nhưng nó có thể dễ dàng hỗ trợ tối ưu hóa. Khi bạn xử lý cải tiến hiệu suất như một loạt các phép tái cấu trúc, bạn có thể tôn trọng Quy tắc Khu cắm trại đồng thời làm cho ứng dụng đi nhanh hơn. Đó thực sự là, ít nhất là theo quan điểm của tôi, về mặt đạo đức khi bạn làm như vậy.

Ví dụ, tác giả của câu hỏi này đã gặp phải một đoạn mã điên rồ. Nếu người này đang đọc mã của tôi, họ sẽ thấy rằng phần điên dài 3-4 dòng. Chính nó trong một phương thức, và tên và mô tả phương thức cho biết phương thức đó đang làm gì. Phương pháp này sẽ chứa 2-6 dòng bình luận nội tuyến mô tả CÁCH mã điên nhận được câu trả lời đúng, mặc dù có vẻ đáng nghi ngờ.

Được ngăn cách theo cách này, bạn có thể tự do trao đổi các triển khai của phương pháp này theo ý muốn. Thật vậy, đó có lẽ là cách tôi viết phiên bản điên rồ để bắt đầu. Bạn được chào đón để thử, hoặc ít nhất là hỏi về các lựa chọn thay thế. Hầu hết thời gian bạn sẽ phát hiện ra rằng việc triển khai ngây thơ tồi tệ hơn đáng kể (thường tôi chỉ bận tâm đến việc cải thiện 2-10x), nhưng trình biên dịch và thư viện luôn thay đổi và ai biết những gì bạn có thể tìm thấy ngày nay không có sẵn khi chức năng đã được viết?


Một chìa khóa chính cho hiệu quả trong nhiều trường hợp là để một mã thực hiện càng nhiều công việc càng tốt theo những cách có thể được thực hiện một cách hiệu quả. Một trong những điều làm tôi khó chịu với .NET là không có cơ chế hiệu quả nào để sao chép một phần của bộ sưu tập này sang bộ sưu tập khác. Hầu hết các bộ sưu tập lưu trữ các nhóm lớn các mục liên tiếp (nếu không phải là toàn bộ) trong các mảng, vì vậy, ví dụ: sao chép 5.000 mục cuối cùng từ danh sách 50.000 mục sẽ phân tách thành một vài thao tác sao chép số lượng lớn (nếu không chỉ một) các bước được thực hiện tối đa một vài lần mỗi lần.
supercat

Thật không may, ngay cả trong trường hợp các hoạt động như vậy có thể được thực hiện một cách hiệu quả, thường sẽ cần phải có các vòng "cồng kềnh" chạy trong 5.000 lần lặp (và trong một số trường hợp là 45.000!). Nếu một hoạt động có thể được giảm xuống thành những thứ như bản sao mảng số lượng lớn, thì những hoạt động đó có thể được tối ưu hóa đến mức độ cực kỳ mang lại kết quả lớn. Nếu mỗi lần lặp lặp cần thực hiện hàng tá bước, thật khó để tối ưu hóa bất kỳ bước nào trong số chúng đặc biệt tốt.
supercat

2

Có lẽ không nên chạm vào nó - nếu mã được viết theo cách đó vì lý do hiệu suất, điều đó có nghĩa là việc thay đổi nó có thể mang lại các vấn đề về hiệu suất đã được giải quyết trước đó.

Nếu bạn làm quyết định thay đổi mọi thứ để có thể đọc nhiều hơn và mở rộng: Trước khi bạn thực hiện một sự thay đổi, chuẩn mã cũ dưới nặng tải. Thậm chí tốt hơn nếu bạn có thể tìm thấy một tài liệu cũ hoặc vé rắc rối mô tả vấn đề hiệu suất mà mã trông kỳ quặc này được cho là sẽ khắc phục. Sau đó, sau khi bạn thực hiện các thay đổi của mình, hãy chạy lại các bài kiểm tra hiệu suất. Nếu nó không khác lắm, hoặc vẫn nằm trong các tham số có thể chấp nhận, thì có lẽ nó ổn.

Đôi khi có thể xảy ra rằng khi các phần khác của hệ thống thay đổi, mã được tối ưu hóa hiệu suất này không còn cần tối ưu hóa nặng nề như vậy nữa, nhưng không có cách nào để biết rằng chắc chắn nếu không có kiểm tra nghiêm ngặt.


1
Một trong những người tôi làm việc cùng bây giờ rất thích tối ưu hóa mọi thứ trong các lĩnh vực mà người dùng đạt được mỗi tháng một lần, nếu điều đó thường xuyên. Nó mất thời gian và không thường xuyên gây ra các vấn đề khác bởi vì anh ấy thích mã hóa và cam kết, và để QA hoặc chức năng hạ nguồn khác thực sự kiểm tra. ::
DaveE

@DaveE: Những tối ưu hóa này được áp dụng bởi vì hoặc các vấn đề hiệu suất thực sự? Hay nhà phát triển này làm điều đó chỉ vì anh ta có thể? Tôi đoán nếu bạn biết các tối ưu hóa sẽ không có tác động, bạn có thể thay thế chúng một cách an toàn bằng mã dễ đọc hơn, nhưng tôi chỉ tin tưởng ai đó là chuyên gia về hệ thống để làm điều đó.
Thất vọngWithFormsDesigner

Họ đã làm xong bởi vì anh ta có thể. Anh ta thực sự thường lưu một số chu kỳ, nhưng khi tương tác của người dùng với phần tử chương trình mất vài giây (15 đến 300 giờ), việc cạo một phần mười giây của thời gian chạy để theo đuổi "hiệu quả" là điều ngớ ngẩn. Đặc biệt là khi những người theo dõi anh ta phải dành thời gian thực sự để hiểu những gì anh ta đã làm. Đây là một ứng dụng PowerBuilder ban đầu được xây dựng 16 năm trước, do đó, đưa ra nguồn gốc của những điều mà suy nghĩ có lẽ là dễ hiểu, nhưng anh từ chối cập nhật suy nghĩ của mình vào thực tế hiện tại.
DaveE

@DaveE: Tôi nghĩ tôi đồng ý nhiều hơn với anh chàng bạn làm việc cùng bạn. Nếu tôi không được phép sửa những thứ chậm mà hoàn toàn không có lý do chính đáng thì tôi sẽ phát điên. Nếu tôi thấy một dòng C ++ liên tục sử dụng toán tử + để tập hợp một chuỗi hoặc mã mở và đọc / dev / urandom mỗi lần qua vòng lặp chỉ vì ai đó quên đặt cờ, thì tôi sẽ sửa nó. Bằng cách cuồng tín về điều này, tôi đã cố gắng tăng tốc, khi những người khác sẽ để nó trượt một micro giây mỗi lần.
Zan Lynx

1
Chà, chúng ta sẽ phải đồng ý không đồng ý. Dành một giờ để thay đổi thứ gì đó để tiết kiệm một phần giây trong thời gian chạy cho một hàm thực thi đôi khi thực sự và để lại mã trong hình dạng đầu cho các nhà phát triển khác là ... không đúng. Nếu đây là các chức năng được thực thi lặp đi lặp lại trong các phần ứng suất cao của ứng dụng, thì tốt & tuyệt vời. Nhưng đó không phải là trường hợp tôi đang mô tả. Đây thực sự là một mã vô cớ, không có lý do nào khác ngoài việc nói "Tôi đã làm điều này mà UserX thực hiện mỗi tuần một lần nhanh hơn một chút". Trong khi đó, chúng tôi có công việc phải trả tiền cần làm.
DaveE

2

Vấn đề ở đây là phân biệt "tối ưu hóa" với có thể đọc và có thể mở rộng, những gì chúng ta là người dùng xem là mã được tối ưu hóa và những gì trình biên dịch xem là tối ưu hóa là hai điều khác nhau. Mã bạn đang xem xét thay đổi có thể không phải là một nút cổ chai, và do đó ngay cả khi mã "nạc", nó thậm chí không cần phải được "tối ưu hóa". Hoặc nếu mã đủ cũ, có thể có các tối ưu hóa được trình biên dịch tạo thành các phần dựng sẵn giúp sử dụng cấu trúc tích hợp đơn giản mới hơn bằng hoặc hiệu quả hơn mã cũ.

Và "nạc", mã không thể đọc được không phải lúc nào cũng được tối ưu hóa.

Tôi đã từng có suy nghĩ rằng mã thông minh / nạc là mã tốt, nhưng đôi khi lợi dụng các quy tắc tối nghĩa của ngôn ngữ gây tổn thương hơn là giúp đỡ trong việc tạo mã, tôi đã bị cắn nhiều hơn không phải trong bất kỳ công việc nhúng nào khi cố gắng hãy khéo léo vì trình biên dịch biến mã thông minh của bạn thành thứ gì đó hoàn toàn không thể sử dụng được bởi phần cứng nhúng.


2

Tôi sẽ không bao giờ thay thế Mã được tối ưu hóa bằng mã có thể đọc được vì tôi không thể thỏa hiệp với hiệu suất và tôi sẽ chọn sử dụng nhận xét phù hợp ở mỗi phần để mọi người có thể hiểu logic được triển khai trong phần Tối ưu hóa đó sẽ giải quyết cả hai vấn đề.

Do đó, Code sẽ được tối ưu hóa + Nhận xét đúng sẽ giúp nó dễ đọc hơn.

LƯU Ý: Bạn có thể làm cho Mã tối ưu hóa có thể đọc được với sự trợ giúp của nhận xét thích hợp nhưng bạn không thể biến Mã có thể đọc thành Mã tối ưu hóa.


Tôi sẽ mệt mỏi với cách tiếp cận này vì tất cả chỉ cần một người chỉnh sửa mã để quên giữ bình luận đồng bộ. Đột nhiên, mỗi lần đánh giá tiếp theo sẽ bỏ đi khi nghĩ rằng nó thực hiện X trong khi thực sự làm Y.
John D

2

Dưới đây là một ví dụ để thấy sự khác biệt giữa mã đơn giản và mã được tối ưu hóa: https://stackoverflow.com/a/11227902/1394264

Đến cuối câu trả lời, anh ta chỉ Thay thế:

if (data[c] >= 128)
    sum += data[c];

với:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

Công bằng mà nói, tôi không biết câu lệnh if đã được thay thế bằng gì nhưng như người trả lời nói rằng một số thao tác bitwise của nó cho kết quả tương tự (tôi sẽ chỉ nhận lời của anh ấy cho nó) .

Điều này thực hiện trong ít hơn một phần tư thời gian ban đầu (11,54 giây so với 2,5 giây)


1

Câu hỏi chính ở đây là: tối ưu hóa là cần thiết?

Nếu có thì bạn không thể thay thế nó bằng mã chậm hơn, dễ đọc hơn. Bạn sẽ cần phải thêm ý kiến ​​vv vào nó để làm cho nó dễ đọc hơn.

Nếu mã không phải được tối ưu hóa thì nó không nên (đến mức ảnh hưởng đến khả năng đọc) và bạn có thể xác định lại mã để làm cho nó dễ đọc hơn.

TUY NHIÊN - đảm bảo bạn biết chính xác những gì mã làm và cách kiểm tra xuyên suốt trước khi bạn bắt đầu thay đổi mọi thứ. Điều này bao gồm việc sử dụng tối đa, vv Nếu không phải soạn một bộ các trường hợp thử nghiệm và chạy chúng trước và sau đó thì bạn không có thời gian để thực hiện tái cấu trúc.


1

Đây là cách tôi làm: Đầu tiên tôi làm cho nó hoạt động theo mã có thể đọc được, sau đó tôi tối ưu hóa nó. Tôi giữ nguồn gốc và ghi lại các bước tối ưu hóa của mình.

Sau đó, khi tôi cần thêm một tính năng, tôi quay lại mã có thể đọc được, thêm tính năng này và làm theo các bước tối ưu hóa mà tôi đã ghi lại. Bởi vì bạn đã ghi lại nó rất nhanh và dễ dàng để mở lại mã của bạn bằng tính năng mới.


0

Khả năng đọc IMHO quan trọng hơn mã được tối ưu hóa vì trong hầu hết các trường hợp, tối ưu hóa vi mô không có giá trị.

Bài viết về tối ưu hóa vi giác quan :

Như hầu hết chúng ta, tôi cảm thấy mệt mỏi khi đọc các bài đăng trên blog về tối ưu hóa vi mô không có ý nghĩa như thay thế in bằng echo, ++ $ i bằng $ i ++ hoặc trích dẫn kép bằng dấu ngoặc đơn. Tại sao? Bởi vì 99.999999% thời gian, nó không liên quan.

"print" sử dụng nhiều opcode hơn "echo" bởi vì nó thực sự trả về một cái gì đó. Chúng ta có thể kết luận rằng tiếng vang nhanh hơn in ấn. Nhưng một opcode không có gì, thực sự không có gì.

Tôi đã thử cài đặt WordPress mới. Kịch bản tạm dừng trước khi kết thúc bằng "Lỗi xe buýt" trên máy tính xách tay của tôi, nhưng số lượng opcodes đã ở mức hơn 2,3 triệu. Đủ nói.


0

Tối ưu hóa là tương đối. Ví dụ:

Hãy xem xét một lớp học với một loạt các thành viên BOOL:

// no nitpicking over BOOL vs bool allowed
class Pear {
 ...
 BOOL m_peeled;
 BOOL m_sliced;
 BOOL m_pitted;
 BOOL m_rotten;
 ...
};

Bạn có thể muốn chuyển đổi các trường BOOL thành bitfield:

class Pear {
 ...
 BOOL m_peeled:1;
 BOOL m_sliced:1;
 BOOL m_pitted:1;
 BOOL m_rotten:1;
 ...
};

Do BOOL được nhập dưới dạng INT (trên nền tảng Windows là số nguyên 32 bit đã ký), nên mất mười sáu byte và gói chúng thành một. Đó là một khoản tiết kiệm 93%! Ai có thể phàn nàn về điều đó?

Giả định này:

Do BOOL được nhập dưới dạng INT (trên nền tảng Windows là số nguyên 32 bit đã ký), nên mất mười sáu byte và gói chúng thành một. Đó là một khoản tiết kiệm 93%! Ai có thể phàn nàn về điều đó?

dẫn đến:

Chuyển đổi BOOL thành trường một bit đã lưu ba byte dữ liệu nhưng bạn phải trả tám byte mã khi thành viên được gán một giá trị không đổi. Tương tự, trích xuất giá trị sẽ đắt hơn.

Những gì đã từng

 push [ebx+01Ch]      ; m_sliced
 call _Something@4    ; Something(m_sliced);

trở thành

 mov  ecx, [ebx+01Ch] ; load bitfield value
 shl  ecx, 30         ; put bit at top
 sar  ecx, 31         ; move down and sign extend
 push ecx
 call _Something@4    ; Something(m_sliced);

Phiên bản bitfield lớn hơn chín byte.

Hãy ngồi xuống và làm một số số học. Giả sử mỗi trường trong số các trường bitfield này được truy cập sáu lần trong mã của bạn, ba lần để viết và ba lần để đọc. Chi phí tăng trưởng mã là khoảng 100 byte. Nó sẽ không chính xác là 102 byte vì trình tối ưu hóa có thể tận dụng các giá trị đã có trong các thanh ghi cho một số thao tác và các hướng dẫn bổ sung có thể có chi phí ẩn về tính linh hoạt của thanh ghi giảm. Sự khác biệt thực tế có thể nhiều hơn, nó có thể ít hơn, nhưng đối với phép tính ngược của phong bì, hãy gọi nó là 100. Trong khi đó, mức tiết kiệm bộ nhớ là 15 byte mỗi lớp. Do đó, điểm hòa vốn là bảy. Nếu chương trình của bạn tạo ít hơn bảy phiên bản của lớp này, thì chi phí mã vượt quá mức tiết kiệm dữ liệu: Tối ưu hóa bộ nhớ của bạn là tối ưu hóa bộ nhớ.

Người giới thiệu

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.