Tổng quát hóa việc sử dụng biến trong mã


11

Tôi muốn biết nếu đó là một thực tiễn tốt để khái quát các biến (sử dụng biến duy nhất để lưu trữ tất cả các giá trị).
Xem xét ví dụ đơn giản

 Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

 Strings query; 
    query= 'Create table XYZ ... ';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

Trong trường hợp đầu tiên tôi sử dụng 4 chuỗi mỗi chuỗi lưu trữ dữ liệu để thực hiện các hành động được đề cập trong hậu tố của chúng.
Trong trường hợp thứ hai chỉ cần 1 biến để lưu trữ tất cả các loại dữ liệu.
Có các biến khác nhau giúp người khác dễ đọc và hiểu nó hơn. Nhưng có quá nhiều trong số họ làm cho nó khó quản lý.

Cũng có quá nhiều biến số cản trở hiệu suất của tôi?

PS: xin đừng trả lời wrt mã trong ví dụ nó chỉ để truyền đạt những gì tôi thực sự có ý nghĩa.


Tất nhiên bạn sử dụng lại cùng một biến ... bởi vì bạn đã xác định nó trong một hàm. Đó là những chức năng dành cho.
zzzzBov

Câu trả lời:


26

Phải tự hỏi mình câu hỏi này là một mùi khá mạnh mà bạn không theo dõi DRY (Đừng lặp lại chính mình). Giả sử bạn có thứ này, trong một ngôn ngữ niềng răng giả định:

function doFoo() {
    query = "SELECT a, b, c FROM foobar WHERE baz = 23";
    result = runQuery(query);
    print(result);

    query = "SELECT foo, bar FROM quux WHERE x IS NULL";
    result = runQuery(query);
    print(result);

    query = "SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10";
    result = runQuery(query);
    print(result);
}

Tái cấu trúc thành:

function runAndPrint(query) {
    result = runQuery(query);
    print(result);
}

function doFoo() {
    runAndPrint("SELECT a, b, c FROM foobar WHERE baz = 23");
    runAndPrint("SELECT foo, bar FROM quux WHERE x IS NULL");
    runAndPrint("SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10");
}

Lưu ý rằng nhu cầu quyết định sử dụng các biến khác nhau có biến mất hay không và làm thế nào bây giờ bạn có thể thay đổi logic để chạy truy vấn và in kết quả ở một nơi, thay vì phải áp dụng cùng một sửa đổi ba lần. (Ví dụ: bạn có thể quyết định bạn muốn bơm kết quả truy vấn thông qua hệ thống mẫu thay vì in ngay lập tức).


2
Tôi chỉ yêu nguyên tắc DRY :)
artjom

1
@tdammers là nó tốt để có chỉ 2 dòng mã bên trong một hàm cân nhắc nếu tôi có chức năng doFoo này () {print (runQuery ( "selct a, b, c từ XYZ"));}?
Shirish11

1
Không, ngăn xếp cuộc gọi không tăng - mỗi cuộc gọi sẽ runAndPrintđẩy một khung ngăn xếp khi bạn gọi nó, và sau đó bật lại khi chức năng thoát. Nếu bạn gọi nó ba lần, nó sẽ thực hiện ba cặp đẩy / pop, nhưng ngăn xếp không bao giờ phát triển nhiều hơn một khung hình cùng một lúc. Bạn chỉ nên thực sự lo lắng về độ sâu ngăn xếp cuộc gọi với các hàm đệ quy.
tdammers

3
Và các hàm chỉ với hai dòng mã là hoàn toàn tốt: nếu hai dòng tạo thành một đơn vị logic, thì đó là hai dòng. Tôi đã viết rất nhiều hàm một lớp, chỉ để giữ một chút thông tin riêng biệt và ở một nơi.
tdammers

1
@JamesAnderson: Đó là một ví dụ hơi khó hiểu, nhưng nó dùng để minh họa một điểm. Đây không phải là về việc bạn có bao nhiêu dòng mã. Đó là bao nhiêu lần bạn nêu thực tế tương tự. Đó là những gì DRY nói về, cũng như nguyên tắc Nguồn đơn Sự thật, quy tắc Không phải Sao chép-Dán, v.v.
tdammers

14

Thông thường, đây là một thực hành xấu .

Sử dụng lại một biến là cách này có thể làm cho mã khó hiểu để đọc một cách hiểu.

Những người đọc mã sẽ không mong đợi một biến được sử dụng lại theo cách như vậy và sẽ không biết tại sao một giá trị được đặt khi bắt đầu có một giá trị khác ở cuối hàm.

Các ví dụ bạn đã đăng rất đơn giản và không thực sự gặp phải vấn đề này, nhưng chúng không đại diện cho một số mã sử dụng lại các biến (nơi nó được đặt khi bắt đầu, được sử dụng lại ở đâu đó ở giữa tầm nhìn).

Các ví dụ bạn đã cho vay để đóng gói vào các hàm, nơi bạn sẽ chuyển qua truy vấn và thực hiện nó.


Hiệu suất hệ thống bị ảnh hưởng bởi nó thì sao?
Shirish11

@ Shirish11 - Nó có thể. Phụ thuộc vào trình biên dịch, ngôn ngữ, môi trường và các biến khác.
Oded

Thông thường, trình biên dịch rất tốt trong việc tối ưu hóa điều này. Tuy nhiên, nó luôn phụ thuộc vào trình biên dịch / tấm / trường hợp / cấu hình cụ thể.
deadalnix

7

Mã tự viết dễ đọc và duy trì hơn

Tuân theo Nguyên tắc tối thiểu hóa và giới luật về tài liệu mã : sử dụng một biến cho một mục tiêu, để cả hai sử dụng dễ hiểu và mã dễ đọc mà không cần giải thích.

Mã cấu trúc chính xác dễ dàng hơn (do đó rẻ hơn) để (Re) sử dụng

Ngoài ra, ở đây nó sẽ xuất hiện queryluôn được sử dụng để chuẩn bị một tuyên bố trước khi thực hiện nó. Đó có thể là một dấu hiệu cho thấy bạn muốn cấu trúc lại một phần của mã này thành một (hoặc nhiều) phương thức trợ giúp để chuẩn bị và thực hiện truy vấn (để tuân thủ nguyên tắc DRY ).

Bằng cách này, bạn sẽ có hiệu quả:

  • chỉ sử dụng một biến trong phương thức trợ giúp của bạn để xác định truy vấn của bối cảnh hiện tại,
  • cần nhập ít mã hơn mỗi lần bạn muốn thực hiện lại truy vấn,
  • làm cho mã của bạn dễ đọc hơn cho người khác.

Ví dụ:

Hãy xem xét điều này, lấy từ ví dụ của bạn, trong đó phiên bản tái cấu trúc rõ ràng là tốt hơn. Tất nhiên đoạn trích của bạn chỉ là một ví dụ cho mục đích của câu hỏi này, nhưng khái niệm này vẫn đúng và quy mô.

Ví dụ của bạn 1:

Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

Ví dụ 2 của bạn:

 Strings query; 
    query= 'Create table XYZ ...';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

Ví dụ 3 (Mã giả tái cấu trúc):

def executeQuery(query, parameters...)
    statement = prepareStatement(query, parameters);
    execute statement;
end

// call point:
executeQuery('Create table XYZ ... ');
executeQuery('Insert into XYZ ...');
executeQuery('Update  XYZ set ...');
executeQuery('Delete from XYZ ...');

Lợi ích cho thấy với việc tái sử dụng thường xuyên.

Giai thoại cá nhân

Ban đầu, tôi bắt đầu là một lập trình viên C làm việc với bất động sản màn hình hạn chế, do đó, việc sử dụng lại các biến có ý nghĩa cho cả mã được biên dịch (trước đó) và để cho phép nhiều mã hơn có thể đọc được cùng một lúc.

Tuy nhiên, sau đó đã chuyển sang các ngôn ngữ cấp cao hơn và tiếp tục lập trình chức năng, tôi có thói quen sử dụng các biến không thay đổi và các tham chiếu bất biến bất cứ khi nào có thể để hạn chế tác dụng phụ.

Có gì trong đó cho tôi?

Nếu bạn có thói quen có tất cả các yếu tố đầu vào của chức năng là bất biến và để trả về một kết quả mới (như một hàm toán học thực sự), bạn sẽ có thói quen không sao chép các cửa hàng.

Bằng cách mở rộng, điều này dẫn đến:

  • bạn viết các hàm ngắn
  • với các mục tiêu được xác định rõ,
  • dễ hiểu hơn
  • tái sử dụng,
  • để mở rộng (cho dù bằng kế thừa OO hoặc bằng chuỗi chức năng),
  • và tài liệu (như đã tự viết tài liệu).

Tôi không nói rằng không có lợi cho trạng thái có thể thay đổi ở đây, tôi chỉ chỉ ra cách thói quen có thể phát triển trên bạn và cách nó ảnh hưởng đến khả năng đọc mã.


2

Về mặt thiết kế mã

Nói chung, có thể sử dụng lại các biến để lưu trữ các giá trị khác nhau - xét cho cùng, đó là lý do tại sao chúng được gọi là biến, bởi vì giá trị được lưu trữ trong chúng khác nhau - miễn là giá trị không chỉ cùng loại mà còn có nghĩa tương tự . Ví dụ: tất nhiên sử dụng lại currentQuerybiến ở đây là ổn:

for currentQuery in queries:
    execute query;

Đương nhiên có một vòng lặp để bạn phải sử dụng lại một biến, nhưng ngay cả khi không có vòng lặp thì nó vẫn ổn. Nếu giá trị không có nghĩa tương tự, hãy sử dụng một biến riêng.

Tuy nhiên, cụ thể, mã bạn đang mô tả trông không được tốt lắm - nó lặp lại . Tốt hơn nhiều là sử dụng các cuộc gọi phương thức vòng lặp hoặc trình trợ giúp (hoặc cả hai). Cá nhân tôi rất hiếm khi thấy mã sản xuất trông giống như phiên bản 1 hoặc 2 của bạn, nhưng trong trường hợp tôi có, tôi nghĩ phiên bản thứ 2 (tái sử dụng biến) là phổ biến hơn.

Về mặt hiệu suất

Nó phụ thuộc vào ngôn ngữ, trình biên dịch và (các) hệ thống thời gian chạy được sử dụng, nhưng nói chung không nên có bất kỳ sự khác biệt nào - đặc biệt là các trình biên dịch cho các máy đăng ký dựa trên ngăn xếp (như x86 / x86-64) sử dụng bất cứ bộ nhớ ngăn xếp miễn phí nào hoặc đăng ký chúng có thể làm mục tiêu gán, hoàn toàn bỏ qua việc bạn có muốn cùng một biến hay không.

Chẳng hạn, gcc -O2tạo ra cùng một nhị phân chính xác và sự khác biệt hiệu suất duy nhất tôi biết là kích thước của bảng ký hiệu trong quá trình biên dịch - hoàn toàn không đáng kể trừ khi bạn quay ngược thời gian về thập niên 60.

Trình biên dịch Java sẽ tạo mã byte cần lưu trữ nhiều hơn cho phiên bản đầu tiên, nhưng jitter của JVM sẽ loại bỏ nó bằng mọi cách, vì vậy, một lần nữa, tôi nghi ngờ rằng thực tế sẽ không có tác động hiệu năng đáng chú ý nào ngay cả khi bạn cần mã được tối ưu hóa cao.


0

Tôi nghĩ rằng việc tái sử dụng biến là tốt trong hầu hết thời gian.

Đối với tôi, tôi chỉ đơn giản là sử dụng lại biến truy vấn hầu hết thời gian. Tôi hầu như luôn luôn thực hiện các truy vấn ngay sau đó. Khi tôi không thực hiện truy vấn ngay lập tức, tôi thường sử dụng một tên biến khác.


-1

Nó có thể tăng sử dụng ngăn xếp nếu trình biên dịch của bạn đặc biệt câm. Cá nhân tôi không nghĩ rằng có một biến riêng cho mỗi truy vấn sẽ thêm vào bất kỳ khả năng đọc nào, bạn vẫn cần phải thực sự nhìn vào chuỗi truy vấn để xem nó làm gì.


Tôi vừa cung cấp một ví dụ đơn giản để người đọc dễ hiểu hơn những gì tôi tìm kiếm. Mã của tôi phức tạp hơn nhiều so với điều này.
Shirish11

-2

Trong ví dụ này, tôi sẽ đi với ví dụ thứ hai. Nó khá rõ ràng cho cả người đọc và tối ưu hóa những gì bạn đang làm. Ví dụ đầu tiên thì đúng hơn một chút và với mã phức tạp hơn một chút tôi sẽ sử dụng nó, nhưng hãy làm như sau:

{
    String query = 'Create table XYZ ...';
    execute query;
}
{
    String query = 'Insert table XYZ ...';
    execute query;
}
And so on...

(Tại thời điểm này, tôi có thể xem xét giải pháp của tdammers .)

Vấn đề với ví dụ đầu tiên querycrelà trong phạm vi cho toàn bộ khối, có thể mở rộng. Điều này có thể gây nhầm lẫn cho ai đó đọc mã. Nó cũng có thể gây nhầm lẫn cho các trình tối ưu hóa, có thể để lại trong bộ nhớ ghi không cần thiết để querycrecó sẵn sau này nếu cần (không phải vậy). Với tất cả các dấu ngoặc nhọn, querychỉ được lưu trữ trong một thanh ghi, nếu vậy.

Với các cụm từ như "Tạo bảng" và "thực thi", nó không giống với tôi như việc ghi thêm bộ nhớ sẽ được chú ý ở đây, vì vậy tôi chỉ lỗi mã gây nhầm lẫn cho người đọc. Nhưng thật hữu ích khi nhận thức được điều này nếu bạn viết mã khi tốc độ không thành vấn đề.


Tôi không đồng ý. Nếu bạn muốn ví dụ thứ hai cho rõ ràng, nó nên được tái cấu trúc thành các cuộc gọi liên tiếp đến một phương thức trợ giúp. nó sẽ truyền đạt nhiều ý nghĩa hơn và đòi hỏi ít mã hơn.
haylem

@haylem: Trong một trường hợp đơn giản thực sự, như thế này, bạn đang thêm một phương thức trợ giúp, mà ai đó đang đọc mã phải đi và tìm. (Và ai đó có thể gặp rắc rối với phương thức của người trợ giúp và phải tìm ra tất cả các địa điểm mà nó được gọi từ đó.) Ít rõ ràng hơn, về cùng một lượng mã. Trong một trường hợp phức tạp hơn, tôi sẽ đi theo giải pháp của mình, sau đó là với tdammer . Tôi đã trả lời câu hỏi này chủ yếu để chỉ ra các vấn đề (thừa nhận mơ hồ, nhưng thú vị) các biến được sử dụng dưới mức gây ra cho cả con người và tối ưu hóa.
RalphChapin

@haylem: bạn và tdammer đều đưa ra giải pháp chính xác. Tôi chỉ nghĩ rằng nó có thể là quá mức trong một số trường hợp.
RalphChapin
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.