Có gì sai với chuỗi ma thuật?


164

Là một nhà phát triển phần mềm có kinh nghiệm, tôi đã học được cách tránh các chuỗi ma thuật.

Vấn đề của tôi là đã rất lâu kể từ khi tôi sử dụng chúng, tôi đã quên hầu hết các lý do tại sao. Kết quả là, tôi gặp khó khăn khi giải thích lý do tại sao chúng là vấn đề với các đồng nghiệp ít kinh nghiệm của tôi.

Những lý do khách quan là có để tránh chúng? Họ gây ra vấn đề gì?


38
Chuỗi ma thuật là gì? Điều tương tự như số ma thuật ?
Laiv

14
@Laiv: Chúng tương tự như số ma thuật, vâng. Tôi thích định nghĩa tại deviq.com/magic-strings : "Chuỗi ma thuật là các giá trị chuỗi được chỉ định trực tiếp trong mã ứng dụng có tác động đến hành vi của ứng dụng.". (Định nghĩa tại en.wikipedia.org/wiki/Magic_opes hoàn toàn không phải là điều tôi nghĩ)
Kramii

17
Điều này thật buồn cười Tôi đã học được cách gièm pha ... sau này Tôi có thể sử dụng những lý lẽ nào để thuyết phục đàn em của mình ... Câu chuyện không bao giờ kết thúc :-). Tôi sẽ không cố gắng "thuyết phục" tôi thà tự học. Không có gì kéo dài hơn một bài học / ý tưởng đạt được bằng kinh nghiệm của chính bạn. Những gì bạn đang cố gắng làm là truyền giáo . Đừng làm điều đó trừ khi bạn muốn có một nhóm Lemmings.
Laiv

15
@Laiv: Tôi muốn cho mọi người học hỏi từ kinh nghiệm của chính họ, nhưng tiếc là đó không phải là một lựa chọn cho tôi. Tôi làm việc cho một bệnh viện được tài trợ công khai, nơi những con bọ tinh vi có thể làm tổn hại đến việc chăm sóc bệnh nhân và nơi chúng tôi không đủ khả năng chi phí bảo trì có thể tránh được.
Kramii

6
@DavidArno, đó chính xác là những gì anh ấy đang làm bằng cách đặt câu hỏi này.
dùng56834

Câu trả lời:


212
  1. Trong ngôn ngữ biên dịch, giá trị của chuỗi ma thuật không được kiểm tra tại thời điểm biên dịch . Nếu chuỗi phải khớp với một mẫu cụ thể, bạn phải chạy chương trình để đảm bảo nó phù hợp với mẫu đó. Nếu bạn đã sử dụng một cái gì đó như enum, giá trị ít nhất là hợp lệ tại thời gian biên dịch, ngay cả khi nó có thể là giá trị sai.

  2. Nếu một chuỗi ma thuật đang được viết ở nhiều nơi, bạn phải thay đổi tất cả chúng mà không có bất kỳ sự an toàn nào (chẳng hạn như lỗi thời gian biên dịch). Điều này có thể được chống lại bằng cách chỉ khai báo nó ở một nơi và sử dụng lại biến.

  3. Typose có thể trở thành lỗi nghiêm trọng. Nếu bạn có một chức năng:

    func(string foo) {
        if (foo == "bar") {
            // do something
        }
    }
    

    và ai đó vô tình gõ:

    func("barr");
    

    Điều này tệ hơn là chuỗi hiếm hơn hoặc phức tạp hơn, đặc biệt nếu bạn có các lập trình viên không quen thuộc với ngôn ngữ bản địa của dự án.

  4. Chuỗi ma thuật hiếm khi tự ghi lại. Nếu bạn thấy một chuỗi, điều đó cho bạn biết không có chuỗi nào khác có thể / nên có. Bạn có thể sẽ phải xem xét việc triển khai để chắc chắn rằng bạn đã chọn đúng chuỗi.

    Loại triển khai đó bị rò rỉ , cần tài liệu bên ngoài hoặc quyền truy cập vào mã để hiểu những gì nên được viết, đặc biệt là vì nó phải hoàn hảo về nhân vật (như ở điểm 3).

  5. Thiếu các hàm "tìm chuỗi" trong IDE, có một số lượng nhỏ các công cụ hỗ trợ mẫu.

  6. Bạn có thể tình cờ sử dụng cùng một chuỗi ma thuật ở hai nơi, khi thực sự chúng là những thứ khác nhau, vì vậy nếu bạn đã thực hiện Tìm & Thay thế và thay đổi cả hai, một trong số chúng có thể bị hỏng trong khi cái kia hoạt động.


34
Về đối số đầu tiên: TypeScript là một ngôn ngữ được biên dịch có thể đánh máy các chuỗi ký tự. Điều này cũng làm mất hiệu lực đối số hai đến bốn. Do đó, không phải chính chuỗi là vấn đề, mà sử dụng một loại cho phép quá nhiều giá trị. Lý do tương tự có thể được áp dụng để sử dụng số nguyên ma thuật cho liệt kê.
Yogu

11
Vì tôi không có kinh nghiệm với TypeScript, tôi sẽ trì hoãn phán quyết của bạn ở đó. Điều tôi muốn nói sau đó là các chuỗi không được kiểm tra (như trường hợp của tất cả các ngôn ngữ tôi đã sử dụng) là vấn đề.
Erdrik Ironrose

23
@Yogu Typecript sẽ không đổi tên tất cả các chuỗi của bạn cho bạn nếu bạn thay đổi loại chuỗi ký tự tĩnh mà bạn mong đợi. Bạn sẽ nhận được các lỗi thời gian biên dịch để giúp bạn tìm thấy tất cả các lỗi đó, nhưng đó chỉ là một cải tiến một phần trên 2. Không nói nó là bất cứ điều gì tuyệt vời hơn (vì đó là, và tôi yêu tính năng này), nhưng nó chắc chắn không hoàn toàn loại bỏ lợi thế của enums Trong dự án của chúng tôi, khi nào nên sử dụng enums và khi nào không phải là một loại câu hỏi kiểu mở mà chúng tôi không chắc chắn; cả hai cách tiếp cận đều có những phiền toái và lợi thế.
KRyan

30
Một điều lớn tôi từng thấy không phải là chuỗi nhiều như số, nhưng có thể xảy ra với chuỗi, là khi bạn có hai giá trị ma thuật có cùng giá trị. Sau đó, một trong số họ thay đổi. Bây giờ bạn đang thực hiện mã thay đổi giá trị cũ thành giá trị mới, hoạt động riêng, nhưng bạn cũng đang thực hiện công việc EXTRA để đảm bảo bạn không thay đổi giá trị sai. Với các biến liên tục, không những bạn không phải trải qua thủ công mà còn không lo lắng rằng mình đã thay đổi điều sai.
corsiKa

35
@Yogu Tôi sẽ lập luận thêm rằng nếu giá trị của một chuỗi ký tự đang được kiểm tra tại thời điểm biên dịch, thì nó không còn là một chuỗi ma thuật . Tại thời điểm đó, nó chỉ là một giá trị const / enum bình thường được viết theo một cách hài hước. Với quan điểm đó, tôi thực sự sẽ lập luận rằng nhận xét của bạn thực sự ủng hộ quan điểm của Erdrik, thay vì bác bỏ chúng.
GrandOpener

89

Đỉnh cao của những gì các câu trả lời khác đã nắm được, không phải là "giá trị ma thuật" là xấu, mà là chúng phải là:

  1. định nghĩa rõ ràng là hằng số;
  2. chỉ được xác định một lần trong toàn bộ miền sử dụng của họ (nếu có thể về mặt kiến ​​trúc);
  3. được định nghĩa cùng nhau nếu chúng tạo thành một tập các hằng số có liên quan đến nhau;
  4. được định nghĩa ở một mức độ tổng quát thích hợp trong ứng dụng mà chúng được sử dụng; và
  5. được định nghĩa theo cách để hạn chế việc sử dụng chúng trong các bối cảnh không phù hợp (ví dụ: có thể sửa đổi kiểu kiểm tra).

Điều thường phân biệt "hằng số" được chấp nhận với "giá trị ma thuật" là một số vi phạm một hoặc nhiều quy tắc này.

Được sử dụng tốt, các hằng chỉ đơn giản cho phép chúng ta thể hiện các tiên đề nhất định của mã.

Điều này đưa tôi đến một điểm cuối cùng, đó là việc sử dụng quá mức các hằng số (và do đó có quá nhiều giả định hoặc ràng buộc được biểu thị theo các giá trị), ngay cả khi nó tuân thủ các tiêu chí trên (nhưng đặc biệt là nếu nó lệch khỏi chúng), có thể ngụ ý rằng giải pháp được đưa ra không đủ chung chung hoặc có cấu trúc tốt (và do đó chúng tôi không thực sự nói về ưu và nhược điểm của các hằng số nữa, mà là về ưu và nhược điểm của mã có cấu trúc tốt).

Các ngôn ngữ cấp cao có cấu trúc cho các mẫu trong các ngôn ngữ cấp thấp hơn sẽ phải sử dụng các hằng số. Các mẫu tương tự cũng có thể được sử dụng trong ngôn ngữ cấp cao hơn, nhưng không nên như vậy.

Nhưng đó có thể là một phán đoán của chuyên gia dựa trên ấn tượng về tất cả các tình huống và giải pháp phải như thế nào, và chính xác cách phán quyết đó sẽ được biện minh sẽ phụ thuộc rất nhiều vào bối cảnh. Thật vậy, nó có thể không chính đáng theo bất kỳ nguyên tắc chung nào, ngoại trừ để khẳng định "Tôi đủ tuổi để xem loại công việc này, mà tôi quen thuộc, làm tốt hơn"!

EDIT: đã chấp nhận một chỉnh sửa, từ chối một chỉnh sửa khác và hiện đã thực hiện chỉnh sửa của riêng tôi, bây giờ tôi có thể xem xét định dạng và dấu chấm câu trong danh sách quy tắc của mình để được giải quyết một lần và mãi mãi haha!


2
Tôi thích câu trả lời này. Sau tất cả "struct" (và mọi từ dành riêng khác) là một chuỗi ma thuật cho trình biên dịch C. Có những cách mã hóa tốt và xấu cho họ.
Alfred Armstrong

6
Ví dụ: nếu ai đó thấy Mã X: = 898755167 * Z mã trong mã của bạn, họ có thể sẽ không biết ý nghĩa của nó và thậm chí ít có khả năng biết rằng nó sai. Nhưng nếu họ nhìn thấy Speed ​​Speed_of_Light: hằng số nguyên: = 299792456 thì ai đó sẽ tìm kiếm nó và đề xuất giá trị chính xác (và thậm chí có thể là loại dữ liệu tốt hơn).
WGroleau

26
Một số người bỏ lỡ điểm hoàn toàn và viết COMMA = "," thay vì SEPARATOR = ",". Cái trước không làm cho mọi thứ rõ ràng hơn, trong khi cái sau nói rõ mục đích sử dụng và cho phép bạn thay đổi dấu phân cách ở một nơi duy nhất.
marcus

1
@marcus, thật đấy! Tất nhiên có một trường hợp sử dụng các giá trị bằng chữ đơn giản tại chỗ - ví dụ: nếu một phương thức chia một giá trị cho hai, thì có thể rõ ràng và đơn giản hơn để viết value / 2, thay vì viết value / VALUE_DIVISORsau được định nghĩa như 2ở nơi khác. Nếu bạn định tổng quát hóa một phương thức xử lý CSV, có lẽ bạn muốn phân tách được truyền vào dưới dạng tham số và hoàn toàn không được định nghĩa là hằng số. Nhưng tất cả chỉ là một câu hỏi về sự phán xét trong bối cảnh - ví dụ về @ WGroleau về SPEED_OF_LIGHTcái gì đó mà bạn muốn đặt tên rõ ràng, nhưng không phải mọi nghĩa đen đều cần điều này.
Steve

4
Câu trả lời hàng đầu tốt hơn câu trả lời này nếu cần thuyết phục rằng chuỗi ma thuật là một "điều xấu". Câu trả lời này sẽ tốt hơn nếu bạn biết và chấp nhận rằng chúng là "điều xấu" và cần tìm ra cách tốt nhất để đáp ứng nhu cầu mà chúng phục vụ một cách có thể duy trì.
corsiKa

34
  • Họ rất khó để theo dõi.
  • Thay đổi tất cả có thể yêu cầu thay đổi nhiều tệp trong nhiều dự án (khó duy trì).
  • Đôi khi thật khó để nói mục đích của họ chỉ là nhìn vào giá trị của họ.
  • Không sử dụng lại.

4
"Không tái sử dụng" nghĩa là gì?
tạm biệt

7
Thay vì tạo một biến / hằng, v.v. và sử dụng lại nó trên tất cả dự án / mã của bạn, bạn đang tạo một chuỗi mới trong mỗi chuỗi gây ra sự trùng lặp không cần thiết.
jason

Vậy điểm 2 và 4 có giống nhau không?
Thomas

4
@ThomasMoors Không có anh ấy nói về cách bạn phải xây dựng một chuỗi mới mỗi khi bạn muốn sử dụng một chuỗi ma thuật đã tồn tại , điểm 2 là về việc thay đổi chính chuỗi đó
Pierre Arlaud

25

Ví dụ thực tế: Tôi đang làm việc với một hệ thống bên thứ ba trong đó "thực thể" được lưu trữ với "các trường". Về cơ bản là một hệ thống EAV . Vì khá dễ dàng để thêm một trường khác, bạn có quyền truy cập vào một trường bằng cách sử dụng tên của trường dưới dạng chuỗi:

Field nameField = myEntity.GetField("ProductName");

(lưu ý chuỗi ma thuật "ProductName")

Điều này có thể dẫn đến một số vấn đề:

  • Tôi cần tham khảo tài liệu bên ngoài để biết rằng "ProductName" thậm chí còn tồn tại và chính tả của nó
  • Thêm vào đó tôi cần tham khảo tài liệu đó để xem kiểu dữ liệu của trường đó là gì.
  • Typose trong chuỗi ma thuật này sẽ không bị bắt cho đến khi dòng mã này được thực thi.
  • Khi ai đó quyết định đổi tên trường này trên máy chủ (khó khăn trong khi ngăn chặn dataloss, nhưng không phải là không thể), thì tôi không thể dễ dàng tìm kiếm thông qua mã của mình để xem nơi tôi nên điều chỉnh tên này.

Vì vậy, giải pháp của tôi cho việc này là tạo các hằng số cho các tên này, được sắp xếp theo loại thực thể. Vì vậy, bây giờ tôi có thể sử dụng:

Field nameField = myEntity.GetField(Model.Product.ProductName);

Nó vẫn là một hằng chuỗi và biên dịch thành cùng một nhị phân, nhưng có một số lợi thế:

  • Sau khi tôi đã gõ "Model.", IDE của tôi chỉ hiển thị các loại thực thể có sẵn, vì vậy tôi có thể dễ dàng chọn "Sản phẩm".
  • Sau đó, IDE của tôi chỉ cung cấp các tên trường có sẵn cho loại thực thể này, cũng có thể chọn.
  • Tài liệu được tạo tự động cho thấy ý nghĩa của trường này là gì với kiểu dữ liệu được sử dụng để lưu trữ các giá trị của nó.
  • Bắt đầu từ hằng số, IDE của tôi có thể tìm thấy tất cả những nơi sử dụng hằng số chính xác đó (trái ngược với giá trị của nó)
  • Typose sẽ bị bắt bởi trình biên dịch. Điều này cũng áp dụng khi một mô hình mới (có thể sau khi đổi tên hoặc xóa một trường) được sử dụng để lấy lại các hằng số.

Tiếp theo trong danh sách của tôi: ẩn các hằng số này phía sau các lớp được gõ mạnh - sau đó cũng là kiểu dữ liệu được bảo mật.


+1 bạn mang đến rất nhiều điểm tốt không giới hạn ở cấu trúc mã: Hỗ trợ và công cụ IDE, có thể là cứu cánh trong các dự án lớn
kmdreko

Nếu một số phần của loại thực thể của bạn đủ tĩnh để thực sự xác định tên hằng cho nó là đáng giá, tôi nghĩ sẽ tốt hơn nếu chỉ xác định một mô hình dữ liệu phù hợp cho nó để bạn có thể làm nameField = myEntity.ProductName;.
Lie Ryan

@LieRyan - việc tạo các hằng số đơn giản và nâng cấp các dự án hiện có để sử dụng chúng dễ dàng hơn nhiều. Điều đó nói rằng, tôi đang làm việc để tạo ra các loại tĩnh để tôi có thể làm chính xác điều đó
Hans Ke st vào

9

Chuỗi ma thuật không phải lúc nào cũng xấu , vì vậy đây có thể là lý do bạn không thể đưa ra lý do chăn để tránh chúng. (Theo "chuỗi ma thuật" Tôi giả sử bạn có nghĩa là chuỗi ký tự là một phần của biểu thức và không được định nghĩa là hằng số.)

Trong một số trường hợp cụ thể, nên tránh các chuỗi ma thuật:

  • Chuỗi tương tự xuất hiện nhiều lần trong mã. Điều này có nghĩa là bạn có thể có một lỗi chính tả một trong những nơi. Và nó sẽ là một rắc rối của các thay đổi chuỗi. Biến chuỗi thành một hằng số, và bạn sẽ tránh được vấn đề này.
  • Chuỗi có thể thay đổi độc lập với mã nơi nó xuất hiện. Ví dụ. nếu chuỗi là văn bản được hiển thị cho người dùng cuối, nó có thể sẽ thay đổi độc lập với bất kỳ thay đổi logic nào. Tách chuỗi đó thành một mô-đun riêng (hoặc cấu hình bên ngoài hoặc cơ sở dữ liệu) sẽ giúp dễ dàng thay đổi độc lập
  • Ý nghĩa của chuỗi không rõ ràng từ ngữ cảnh. Trong trường hợp đó, việc giới thiệu một hằng số sẽ làm cho mã dễ hiểu hơn.

Nhưng trong một số trường hợp, "chuỗi ma thuật" vẫn ổn. Giả sử bạn có một trình phân tích cú pháp đơn giản:

switch (token.Text) {
  case "+":
    return a + b;
  case "-":
    return a - b;
  //etc.
}

Thực sự không có phép thuật nào ở đây, và không có vấn đề nào được mô tả ở trên áp dụng. Sẽ không có lợi ích IMHO để xác định, string Plus="+"vv Giữ cho nó đơn giản.


7
Tôi nghĩ định nghĩa của bạn về "chuỗi ma thuật" là không đủ, nó cần phải có một số khái niệm về ẩn / che khuất / làm cho bí ẩn. Tôi sẽ không đề cập đến "+" và "-" trong ví dụ ngược lại đó là "ma thuật", bất kỳ điều gì nhiều hơn tôi muốn nói đến số 0 là ma thuật trong if (dx != 0) { grad = dy/dx; }.
Rupe

2
@Rupe: Tôi đồng ý, nhưng OP sử dụng định nghĩa " các giá trị chuỗi được chỉ định trực tiếp trong mã ứng dụng có tác động đến hành vi của ứng dụng. " Không yêu cầu chuỗi phải bí ẩn, vì vậy đây là định nghĩa tôi sử dụng trong câu trả lời.
JacquesB

7
Với tham chiếu đến ví dụ của bạn, tôi đã thấy các câu lệnh chuyển đổi được thay thế "+""-"bằng TOKEN_PLUSTOKEN_MINUS. Mỗi lần đọc nó tôi cảm thấy như khó đọc và gỡ lỗi hơn vì nó! Chắc chắn là một nơi mà tôi đồng ý rằng sử dụng các chuỗi đơn giản là tốt hơn.
Cort Ammon

2
Tôi đồng ý rằng có những lúc chuỗi ma thuật phù hợp: tránh chúng là quy tắc của ngón tay cái và tất cả các quy tắc của ngón tay cái đều có ngoại lệ. Hy vọng rằng, khi chúng ta hiểu rõ lý do tại sao chúng có thể là một điều xấu, chúng ta sẽ có thể đưa ra những lựa chọn thông minh, thay vì làm mọi việc vì (1) chúng ta chưa bao giờ hiểu rằng có thể có một cách tốt hơn, hoặc (2) chúng ta Đã được yêu cầu làm những điều khác biệt bởi một nhà phát triển cao cấp hoặc một tiêu chuẩn mã hóa.
Kramii

2
Tôi không biết "ma thuật" ở đây là gì. Chúng trông giống như chuỗi ký tự cơ bản đối với tôi.
tchrist

6

Để thêm vào câu trả lời hiện có:

Quốc tế hóa (i18n)

Nếu văn bản hiển thị trên màn hình được mã hóa cứng và bị chôn vùi trong các lớp chức năng, bạn sẽ gặp khó khăn khi cung cấp bản dịch văn bản đó sang các ngôn ngữ khác.

Một số môi trường phát triển (ví dụ Qt) xử lý các bản dịch bằng cách tra cứu từ chuỗi văn bản ngôn ngữ cơ sở sang ngôn ngữ dịch. Chuỗi ma thuật nói chung có thể tồn tại điều này - cho đến khi bạn quyết định bạn muốn sử dụng cùng một văn bản ở nơi khác và nhận được một lỗi đánh máy. Thậm chí sau đó, rất khó để tìm ra chuỗi ma thuật nào cần dịch khi bạn muốn thêm hỗ trợ cho ngôn ngữ khác.

Một số môi trường phát triển (ví dụ MS Visual Studio) thực hiện một cách tiếp cận khác và yêu cầu tất cả các chuỗi dịch được giữ trong cơ sở dữ liệu tài nguyên và đọc lại ngôn ngữ hiện tại bằng ID duy nhất của chuỗi đó. Trong trường hợp này, ứng dụng của bạn với các chuỗi ma thuật đơn giản là không thể dịch sang ngôn ngữ khác mà không cần làm lại chính. Phát triển hiệu quả đòi hỏi tất cả các chuỗi văn bản phải được nhập vào cơ sở dữ liệu tài nguyên và được cấp một ID duy nhất khi mã được viết lần đầu tiên và sau đó i18n tương đối dễ dàng. Cố gắng lấp đầy điều này sau khi thực tế thường đòi hỏi một nỗ lực rất lớn (và vâng, tôi đã ở đó!) Vì vậy tốt hơn hết là làm mọi việc ngay từ đầu.


3

Đây không phải là ưu tiên của tất cả mọi người, nhưng nếu bạn muốn tính toán các số liệu về khớp nối / kết dính trên mã của mình theo cách tự động, chuỗi ma thuật làm cho điều này gần như không thể. Một chuỗi ở một nơi sẽ tham chiếu đến một lớp, phương thức hoặc hàm ở một nơi khác và không có cách nào dễ dàng, tự động để xác định rằng chuỗi được ghép với lớp / phương thức / hàm chỉ bằng cách phân tích mã. Chỉ khung cơ bản (Angular, ví dụ) có thể xác định rằng có một mối liên kết - và nó chỉ có thể thực hiện nó trong thời gian chạy. Để tự lấy thông tin ghép nối, trình phân tích cú pháp của bạn sẽ phải biết mọi thứ về khung công tác bạn đang sử dụng, bên trên và ngoài ngôn ngữ cơ bản mà bạn đang mã hóa.

Nhưng một lần nữa, đây không phải là điều mà rất nhiều nhà phát triển quan tâm.

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.