hiệu suất so với tái sử dụng


8

Làm thế nào tôi có thể viết các chức năng có thể tái sử dụng mà không làm giảm hiệu suất? Tôi liên tục gặp phải tình huống tôi muốn viết một hàm theo cách làm cho nó có thể sử dụng lại được (ví dụ: nó không đưa ra giả định nào về môi trường dữ liệu) nhưng biết dòng chảy chung của chương trình tôi biết nó không hiệu quả nhất phương pháp. Ví dụ: nếu tôi muốn viết một hàm xác nhận mã chứng khoán nhưng có thể sử dụng lại, tôi không thể giả sử rằng recordset đang mở. Tuy nhiên, nếu tôi mở và đóng recordset mỗi khi hàm được gọi thì hiệu năng đạt được khi lặp qua hàng ngàn hàng có thể rất lớn.

Vì vậy, đối với hiệu suất tôi có thể có:

Function IsValidStockRef(strStockRef, rstStockRecords)
    rstStockRecords.Find ("stockref='" & strStockRef & "'")
    IsValidStockRef = Not rstStockRecords.EOF
End Function

Nhưng để tái sử dụng tôi sẽ cần một cái gì đó như sau:

Function IsValidStockRef(strStockRef)
    Dim rstStockRecords As ADODB.Recordset

    Set rstStockRecords = New ADODB.Recordset
    rstStockRecords.Open strTable, gconnADO

    rstStockRecords.Find ("stockref='" & strStockRef & "'")
    IsValidStockRef = Not rstStockRecords.EOF

    rstStockRecords.Close
    Set rstStockRecords = Nothing
End Function

Tôi lo ngại rằng tác động đến hiệu suất mở và đóng bản ghi đó khi được gọi từ trong vòng lặp qua hàng ngàn hàng / bản ghi sẽ rất nghiêm trọng nhưng sử dụng phương pháp đầu tiên làm cho chức năng này ít được sử dụng lại.

Tôi nên làm gì?


Câu trả lời:


13

Bạn nên làm bất cứ điều gì mang lại giá trị kinh doanh lớn hơn trong tình huống này.

Viết phần mềm luôn là một sự đánh đổi. Hầu như không bao giờ là tất cả các mục tiêu hợp lệ (khả năng duy trì, hiệu suất, sự rõ ràng, tính đồng nhất, bảo mật, v.v.) hoàn toàn phù hợp. Đừng rơi vào cái bẫy của những người thiển cận, những người coi một trong những chiều này là tối quan trọng và bảo bạn hãy hy sinh mọi thứ cho nó.

Thay vào đó, hãy hiểu những rủi ro và lợi ích của mỗi ưu đãi thay thế, định lượng chúng và đi cùng với những gì tối đa hóa kết quả. (Tất nhiên, bạn không cần phải ước tính bằng số. Nó đủ để cân nhắc các yếu tố như "sử dụng lớp này có nghĩa là khóa chúng tôi vào thuật toán băm đó, nhưng vì chúng tôi không sử dụng nó để bảo vệ chống lại cuộc tấn công độc hại , chỉ cho tiện lợi, cái này đủ tốt để chúng ta có thể bỏ qua cơ hội va chạm vô tình 1: 1.000.000.000 ".)

Điều quan trọng nhất là hãy nhớ rằng họ đang đánh đổi; không một nguyên tắc nào biện minh cho tất cả mọi thứ để thỏa mãn, và không có quyết định nào, một khi được thực hiện, cần phải đứng vững vĩnh viễn . Bạn luôn có thể phải xem xét lại trong nhận thức muộn khi hoàn cảnh thay đổi theo cách bạn không thấy trước. Đó là một nỗi đau, nhưng gần như không đau đớn bằng việc đưa ra quyết định tương tự mà không có sự nhận thức muộn màng.


2
Mặc dù những gì bạn nói là đúng nói chung, nên nói điều gì đó để viết mã để bảo vệ chống lại mọi trường hợp có thể xảy ra so với các điều kiện tiên quyết. Theo ý kiến ​​khiêm tốn của tôi, không có gì sai khi chỉ đơn giản là mong đợi rằng recordset đã được mở khi được gọi, được cung cấp đủ tài liệu. Nếu bất cứ điều gì, nếu đây là một phương thức trong thư viện, hãy thực hiện kiểm tra nhanh xem nó có mở không và nếu không, hãy ném ngoại lệ. Không cần phải "làm cho nó hoạt động" trong bất kỳ kịch bản có thể.
Neil

6

Không cái nào trong số này có vẻ tái sử dụng hơn cái kia. Họ dường như chỉ ở mức độ trừu tượng khác nhau . Đầu tiên là để gọi mã hiểu hệ thống chứng khoán đủ sâu sắc để biết rằng xác nhận tham chiếu chứng khoán có nghĩa là xem qua một Recordsetvới một loại truy vấn. Thứ hai là để gọi mã chỉ muốn biết liệu mã chứng khoán có hợp lệ hay không và không quan tâm đến việc bạn xác minh điều đó như thế nào.

Nhưng như với hầu hết các khái niệm trừu tượng , cái này là "rò rỉ". Trong trường hợp này, sự trừu tượng bị rò rỉ thông qua hiệu năng của nó - mã gọi hoàn toàn không thể bỏ qua cách xác thực được thực hiện bởi vì nếu có, nó có thể gọi hàm đó hàng ngàn lần như bạn mô tả và làm giảm nghiêm trọng hiệu suất tổng thể.

Cuối cùng, nếu bạn phải chọn giữa mã kém trừu tượng và hiệu năng không được chấp nhận, bạn phải chọn mã kém trừu tượng. Nhưng trước tiên, bạn nên tìm kiếm một giải pháp tốt hơn - một sự thỏa hiệp duy trì hiệu suất chấp nhận được và thể hiện sự trừu tượng hóa (nếu không lý tưởng). Thật không may, tôi không biết rõ về VBA, nhưng trong ngôn ngữ OO, suy nghĩ đầu tiên của tôi là cung cấp mã gọi cho một lớp với các phương thức như:

BeginValidation()
IsValidStockRef(strStockRef)
EndValidation()

Ở đây Begin...End...các phương thức của bạn thực hiện việc quản lý vòng đời một lần của bộ bản ghi, IsValidStockRefkhớp với phiên bản đầu tiên của bạn, nhưng sử dụng bộ bản ghi này mà chính lớp đó đã chịu trách nhiệm, thay vì phải truyền vào. Mã gọi sau đó sẽ gọi Begin...End...các phương thức bên ngoài vòng lặp và phương thức xác nhận bên trong.

Lưu ý: Đây chỉ là một ví dụ minh họa rất thô sơ và có thể được coi là bước đầu tiên khi tái cấu trúc. Các tên có thể có thể sử dụng điều chỉnh và tùy thuộc vào ngôn ngữ nên có một cách rõ ràng hơn hoặc thành ngữ hơn để làm điều đó (ví dụ C # có thể sử dụng hàm tạo để bắt đầu và Dispose()kết thúc). Mã lý tưởng chỉ muốn kiểm tra xem một giới thiệu chứng khoán có hợp lệ không nên tự mình thực hiện bất kỳ quản lý vòng đời nào.

Điều này thể hiện một sự suy giảm nhẹ đối với sự trừu tượng mà chúng tôi đang trình bày: bây giờ gọi mã cần phải biết đủ về xác nhận để hiểu rằng đó là một cái gì đó đòi hỏi một số loại thiết lập và phá vỡ. Nhưng để đáp lại sự thỏa hiệp tương đối khiêm tốn đó, giờ đây chúng ta có các phương thức có thể được sử dụng dễ dàng bằng cách gọi mã, mà không ảnh hưởng đến hiệu suất của chúng tôi.


Downvoter: Bất kỳ lý do cụ thể, không quan tâm?
Ben Aaronson

Tôi không phải là người hạ cấp, nhưng tôi sẽ đoán. BeginValidation, EndValidationIsValidStockRefcó mối quan hệ đặc biệt với nhau. Kiến thức về mối quan hệ đó phức tạp hơn kiến ​​thức cần có để xử lý trực tiếp a RecordSet. Và kiến ​​thức cần thiết để xử lý a RecordSetđược áp dụng rộng rãi hơn.
Keen

@Cory Tôi đồng ý ở một mức độ nào đó, và bàn tay của tôi đã bị ép buộc một chút do thiếu kiến ​​thức về vba. Tôi đã cố gắng chỉ ra điều này bằng câu tiếp theo, nhưng có lẽ cách diễn đạt của tôi không rõ ràng hoặc đủ mạnh. Tôi đã thực hiện một chỉnh sửa để cố gắng làm cho điều này rõ ràng hơn một chút
Ben Aaronson

Một lưu ý thú vị, trong C #, bạn sẽ được yêu cầu sử dụng usingcâu lệnh để thực hiện công việc này. Trong các ngôn ngữ khác (dù sử dụng ngoại lệ nào), để thực hiện công việc tương tự using, bạn cần sử dụng try {} finally {}để đảm bảo xử lý đúng cách và thậm chí đôi khi không thể bọc chính xác tất cả các mã có thể throw. Đây là một vấn đề tiềm năng với tất cả các giải pháp được đề cập ở đây và tôi cũng không chắc chắn cách giải quyết vấn đề này trong VBA.
Keen

@Cory: Và trong C ++, bạn chỉ cần sử dụng RAII.
Ded repeatator

3

Trong một thời gian dài, tôi đã từng thực hiện một hệ thống kiểm tra phức tạp để có thể sử dụng các giao dịch cơ sở dữ liệu. Logic giao dịch diễn ra như sau: mở một giao dịch, thực hiện các hoạt động cơ sở dữ liệu, phục hồi khi có lỗi hoặc cam kết thành công. Sự phức tạp đến từ những gì xảy ra khi bạn muốn một hoạt động bổ sung được thực hiện trong cùng một giao dịch. Bạn cần phải viết một phương thức thứ hai hoàn toàn cho cả hai thao tác hoặc bạn có thể gọi phương thức ban đầu của mình từ một giây, chỉ mở một giao dịch nếu một phương thức chưa được mở và cam kết / quay lại thay đổi chỉ khi bạn là một để mở giao dịch.

Ví dụ:

public void method1() {
    boolean selfOpened = false;
    if(!transaction.isOpen()) {
        selfOpened = true;
        transaction.open();
    }

    try {
        performDbOperations();
        method2();

        if(selfOpened) 
            transaction.commit();
    } catch (SQLException e) {
        if(selfOpened) 
            transaction.rollback();
        throw e;
    }
}

public void method2() {
    boolean selfOpened = false;
    if(!transaction.isOpen()) { 
        selfOpened = true;
        transaction.open();
    }

    try {
        performMoreDbOperations();

        if(selfOpened) 
            transaction.commit();
    } catch (SQLException e) {
        if(selfOpened) 
            transaction.rollback();
        throw e;
    }
}

Xin lưu ý, tôi không ủng hộ mã trên bằng bất kỳ phương tiện nào. Điều này sẽ phục vụ như là một ví dụ về những gì không nên làm!

Có vẻ ngớ ngẩn khi tạo ra một phương thức thứ hai để thực hiện logic tương tự như phương pháp thứ nhất cộng thêm thứ gì đó, nhưng tôi muốn có thể gọi phần API cơ sở dữ liệu của chương trình và loại bỏ các vấn đề ở đó. Tuy nhiên, trong khi điều này giải quyết được một phần vấn đề của tôi, mọi phương pháp tôi đã viết đều liên quan đến việc thêm logic dài dòng này để kiểm tra xem một giao dịch đã mở chưa, và cam kết / khôi phục thay đổi nếu phương thức của tôi mở nó.

Vấn đề là khái niệm. Tôi không nên cố gắng nắm lấy mọi kịch bản có thể. Cách tiếp cận đúng là sử dụng logic giao dịch nội bộ trong một phương thức duy nhất lấy phương thức thứ hai làm tham số sẽ thực hiện logic cơ sở dữ liệu thực tế. Logic đó giả định rằng giao dịch là mở và thậm chí không thực hiện kiểm tra. Các phương thức này có thể được gọi kết hợp để các phương thức này không bị lộn xộn với logic giao dịch không cần thiết.

Lý do tôi đề cập đến điều này là vì sai lầm của tôi là cho rằng tôi cần phải làm cho phương pháp của mình hoạt động trong mọi tình huống. Khi làm như vậy, không chỉ phương thức được gọi của tôi là kiểm tra nếu một giao dịch được mở, mà cả những phương thức mà nó được gọi. Trong trường hợp này, nó không phải là một thành tích lớn, nhưng nếu nói, tôi cần xác minh sự tồn tại của một bản ghi trong cơ sở dữ liệu trước khi tiếp tục, tôi sẽ kiểm tra mọi phương thức yêu cầu khi tôi chỉ cần giả định tất cả người gọi nên được biết rằng hồ sơ nên tồn tại. Nếu phương thức được gọi bằng mọi cách, đây là hành vi không xác định và bạn không cần phải lo lắng về những gì sẽ xảy ra.

Thay vào đó, bạn nên cung cấp nhiều tài liệu và viết những gì bạn mong đợi là đúng trước khi thực hiện cuộc gọi theo phương thức của bạn. Nếu nó đủ quan trọng, hãy thêm nó dưới dạng một nhận xét trước phương thức của bạn để không có lỗi (javadoc cung cấp hỗ trợ tốt cho loại điều này trong java).

Tôi hy vọng điều đó sẽ giúp!


2

Bạn có thể có hai chức năng quá tải. Bằng cách đó bạn có thể sử dụng cả hai theo tình huống.

Bạn không bao giờ (tôi chưa bao giờ thấy điều đó xảy ra) tối ưu hóa cho mọi thứ, vì vậy bạn phải giải quyết một cái gì đó. Chọn những gì bạn tin là quan trọng hơn.


Thật không may, tôi đang làm rất nhiều trong VBA và quá tải không phải là một lựa chọn. Tôi có thể sử dụng một Optionaltham số mặc dù để đạt được một hiệu ứng tương tự.
Caltor

2

2 chức năng: một chức năng mở recordset và chuyển nó đến chức năng phân tích dữ liệu.

Đầu tiên có thể được bỏ qua nếu bạn đã có một recordset mở. Thứ hai có thể giả định rằng nó sẽ được thông qua một tập bản ghi mở, bỏ qua nó đến từ đâu và xử lý dữ liệu.

Bạn có cả hiệu suất và khả năng sử dụng lại sau đó!


Tôi không nghĩ cần phải mở recordset cho người gọi, nhưng nếu không thì tôi đồng ý.
Neil

0

Tối ưu hóa (bên cạnh tối ưu hóa vi mô) là trực tiếp mâu thuẫn với tính mô đun.

Tính mô đun hoạt động bằng cách tách mã khỏi bối cảnh toàn cầu, trong khi tối ưu hóa hiệu suất khai thác bối cảnh toàn cầu để giảm thiểu những gì mã phải làm. Tính mô đun là lợi ích của khớp nối thấp, trong khi (tiềm năng) hiệu suất rất cao là lợi ích của khớp nối cao.

Câu trả lời là kiến ​​trúc. Hãy xem xét các đoạn mã mà bạn sẽ muốn sử dụng lại. Có lẽ đó là thành phần tính toán giá hoặc logic xác thực cấu hình.

Sau đó, bạn nên viết mã tương tác với thành phần đó để có thể sử dụng lại. Trong một thành phần mà bạn không bao giờ có thể sử dụng chỉ một phần của mã bạn có thể tối ưu hóa cho hiệu suất vì bạn biết sẽ không có ai khác sử dụng nó.

Thủ thuật của nó là xác định thành phần của bạn là gì.

tl; dr: giữa các thành phần ghi với mô đun trong tâm trí, trong các thành phần viết với hiệu suất trong tâm trí.


Tính mô đun và tối ưu hóa không nhất thiết là ở tỷ lệ cược. Các trình biên dịch hiện đại có thể nội tuyến hầu như mọi thứ ở bất cứ đâu, vì vậy, cho dù bạn viết mô-đun như thế nào, miễn là trình biên dịch có thể kết hợp nó với một chương trình thực thi không mô-đun, không có lý do gì nó có thể nhanh như mã được viết không mô-đun ở nơi đầu tiên. Tất nhiên, không phải tất cả các trình biên dịch có thể làm điều này rất tốt, nhưng ...
leftaroundabout

@leftaroundabout Ý tôi là ở cấp mã nguồn, nhưng bạn nói rất đúng. Không có lý do gì một trình biên dịch đủ thông minh không thể thay thế sắp xếp bong bóng của bạn bằng một loại nhanh chóng!
Sled
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.