Làm thế nào để tôi lưu trữ các giá trị không xác định được và các vùng bị thiếu trong một biến, trong khi vẫn giữ được sự khác biệt giữa


57

Hãy coi đây là một câu hỏi "hàn lâm". Thỉnh thoảng tôi đã tự hỏi về việc tránh các NULL và đây là một ví dụ mà tôi không thể đưa ra một giải pháp thỏa đáng.


Giả sử tôi lưu trữ các phép đo trong trường hợp phép đo được biết là không thể (hoặc thiếu). Tôi muốn lưu trữ giá trị "trống" đó trong một biến trong khi tránh NULL. Lần khác giá trị có thể không được biết. Vì vậy, có các phép đo trong một khung thời gian nhất định, một truy vấn về phép đo trong khoảng thời gian đó có thể trả về 3 loại phản hồi:

  • Phép đo thực tế tại thời điểm đó (ví dụ: bất kỳ giá trị số nào bao gồm 0)
  • Giá trị "thiếu" / "trống" (nghĩa là đã thực hiện phép đo và giá trị được biết là trống tại thời điểm đó).
  • Một giá trị không xác định (nghĩa là không có phép đo nào được thực hiện tại thời điểm đó. Nó có thể trống, nhưng nó cũng có thể là bất kỳ giá trị nào khác).

Làm rõ quan trọng:

Giả sử bạn có một hàm get_measurement()trả về một trong số "trống", "không xác định" và giá trị của loại "số nguyên". Có một giá trị số ngụ ý rằng các hoạt động nhất định có thể được thực hiện trên giá trị trả về (nhân, chia, ...) nhưng sử dụng các hoạt động đó trên NULL sẽ làm hỏng ứng dụng nếu không bắt được.

Tôi muốn có thể viết mã, tránh kiểm tra NULL, ví dụ (mã giả):

>>> value = get_measurement()  # returns `2`
>>> print(value * 2)
4

>>> value = get_measurement()  # returns `Empty()`
>>> print(value * 2)
Empty()

>>> value = get_measurement()  # returns `Unknown()`
>>> print(value * 2)
Unknown()

Lưu ý rằng không có printcâu lệnh nào gây ra ngoại lệ (vì không có NULL nào được sử dụng). Vì vậy, các giá trị trống & không xác định sẽ lan truyền khi cần thiết và kiểm tra xem một giá trị thực sự là "không xác định" hay "trống" có thể bị trì hoãn cho đến khi thực sự cần thiết (như lưu trữ / tuần tự hóa giá trị ở đâu đó).


Lưu ý bên lề: Lý do tôi muốn tránh NULL, chủ yếu là một lời trêu ghẹo não. Nếu tôi muốn hoàn thành công việc, tôi không phản đối việc sử dụng NULL, nhưng tôi thấy rằng việc tránh chúng có thể giúp mã mạnh hơn rất nhiều trong một số trường hợp.


19
Tại sao bạn muốn phân biệt "đo lường được thực hiện nhưng giá trị trống" so với "không đo lường"? Trong thực tế, "đo lường được thực hiện nhưng giá trị trống" có nghĩa là gì? Có phải cảm biến không tạo ra một giá trị hợp lệ? Trong trường hợp đó, nó khác với "không biết" như thế nào? Bạn sẽ không thể quay ngược thời gian và nhận được giá trị chính xác.
DaveG

3
@DaveG Giả sử tìm nạp số lượng CPU trong máy chủ. Nếu máy chủ bị tắt hoặc đã bị loại bỏ, giá trị đó đơn giản là không tồn tại. Đây sẽ là một phép đo không có ý nghĩa gì (có thể "thiếu" / "trống" không phải là thuật ngữ tốt nhất). Nhưng giá trị được "biết" là vô nghĩa. Nếu máy chủ tồn tại, nhưng quá trình tìm nạp giá trị gặp sự cố, đo nó là hợp lệ, nhưng không thành công dẫn đến giá trị "không xác định".
shoutuma

2
@exhuma Tôi sẽ mô tả nó là "không áp dụng", sau đó.
Vincent

6
Vì tò mò, bạn đang thực hiện loại phép đo nào trong đó "trống" không đơn giản bằng số 0 của bất kỳ thang đo nào? "Không xác định" / "thiếu" Tôi có thể thấy hữu ích, ví dụ: nếu cảm biến không được nối hoặc nếu đầu ra thô của cảm biến là rác vì lý do này hay lý do khác, nhưng "trống" trong mọi trường hợp tôi có thể nghĩ có thể nhất quán hơn đại diện bởi 0, []hoặc {}(vô hướng 0, danh sách trống và bản đồ trống, tương ứng). Ngoài ra, giá trị "thiếu" / "không xác định" đó về cơ bản chính xác nulllà dành cho - nó thể hiện rằng có thể có một đối tượng ở đó, nhưng không có.
Nic Hartley

7
Bất cứ giải pháp nào bạn sử dụng cho việc này, hãy chắc chắn tự hỏi liệu nó có gặp phải vấn đề tương tự với những vấn đề khiến bạn muốn loại bỏ NULL ngay từ đầu không.
Ray

Câu trả lời:


85

Cách phổ biến để làm điều này, ít nhất là với các ngôn ngữ chức năng là sử dụng một liên minh phân biệt đối xử. Đây là một giá trị là một trong một int hợp lệ, một giá trị biểu thị "thiếu" hoặc một giá trị biểu thị "không xác định". Trong F #, nó có thể trông giống như:

type Measurement =
    | Reading of value : int
    | Missing
    | Unknown of value : RawData

Một Measurementgiá trị sau đó sẽ là một Reading, với một giá trị int, hoặc một Missing, hoặc một Unknownvới các dữ liệu thô như value(nếu cần).

Tuy nhiên, nếu bạn không sử dụng ngôn ngữ hỗ trợ các hiệp hội bị phân biệt đối xử hoặc tương đương với họ, thì mẫu này không có khả năng sử dụng nhiều cho bạn. Vì vậy, ở đó, bạn có thể sử dụng một lớp với trường enum biểu thị cái nào trong ba cái chứa dữ liệu chính xác.


7
bạn có thể thực hiện các loại tổng bằng ngôn ngữ OO nhưng có một chút hợp lý của tấm nồi hơi để làm cho chúng hoạt động stackoverflow.com/questions/3151702/
jk.

11
Tên [trong các ngôn ngữ không chức năng] mẫu này không có khả năng sử dụng nhiều cho bạn - Đây là một mẫu khá phổ biến trong OOP. GOF có một biến thể của mẫu này và các ngôn ngữ như C ++ cung cấp các cấu trúc gốc để mã hóa nó.
Konrad Rudolph

14
@jk. Vâng, họ không tính (tôi đoán họ cũng vậy; họ rất tệ trong kịch bản này do thiếu an toàn). Tôi có nghĩa là std::variant(và tiền thân tinh thần của nó).
Konrad Rudolph

2
@Ewan Không, người ta nói rằng Đo lường là một kiểu dữ liệu có thể là hoặc hoặc.
Konrad Rudolph

2
@DavidArno Ngay cả khi không có DU, vẫn có một giải pháp chính quy của YouTube cho OOP, đó là có một siêu lớp các giá trị với các lớp con cho các giá trị hợp lệ và không hợp lệ. Nhưng điều đó có thể đi quá xa (và trong thực tế, có vẻ như hầu hết các mã dựa trên sự đa hình của lớp con eschew ủng hộ một lá cờ cho điều này, như thể hiện trong các câu trả lời khác).
Konrad Rudolph

58

Nếu bạn chưa biết một đơn vị là gì, hôm nay sẽ là một ngày tuyệt vời để học hỏi. Tôi có một giới thiệu nhẹ nhàng cho các lập trình viên OO ở đây:

https://ericlippert.com/2013/02/21/monads-part-one/

Kịch bản của bạn là một phần mở rộng nhỏ cho "có thể đơn nguyên", còn được gọi là Nullable<T>trong C # và Optional<T>trong các ngôn ngữ khác.

Giả sử bạn có một loại trừu tượng để đại diện cho đơn nguyên:

abstract class Measurement<T> { ... }

và sau đó ba lớp con:

final class Unknown<T> : Measurement<T> { ... a singleton ...}
final class Empty<T> : Measurement<T> { ... a singleton ... }
final class Actual<T> : Measurement<T> { ... a wrapper around a T ...}

Chúng tôi cần triển khai Bind:

abstract class Measurement<T>
{ 
    public Measurement<R> Bind(Func<T, Measurement<R>> f)
  {
    if (this is Unknown<T>) return Unknown<R>.Singleton;
    if (this is Empty<T>) return Empty<R>.Singleton;
    if (this is Actual<T>) return f(((Actual<T>)this).Value);
    throw ...
  }

Từ đây bạn có thể viết phiên bản đơn giản hóa này của Bind:

public Measurement<R> Bind(Func<A, R> f) 
{
  return this.Bind(a => new Actual<R>(f(a));
}

Và bây giờ bạn đã hoàn thành. Bạn có Measurement<int>trong tay. Bạn muốn nhân đôi nó:

Measurement<int> m = whatever;
Measurement<int> doubled = m.Bind(a => a * 2);
Measurement<string> asString = m.Bind(a => a.ToString());

Và theo logic; nếu mEmpty<int>thì asStringEmpty<String>, xuất sắc.

Tương tự, nếu chúng ta có

Measurement<int> First()

Measurement<double> Second(int i);

sau đó chúng ta có thể kết hợp hai phép đo:

Measurement<double> d = First().Bind(Second);

và một lần nữa, nếu First()Empty<int>sau đó dEmpty<double>và vân vân.

Bước quan trọng là để có được các hoạt động liên kết chính xác . Hãy suy nghĩ kỹ về nó.


4
Monads (rất may) dễ sử dụng hơn nhiều so với hiểu. :)
Guran

11
@leftaroundabout: Chính xác là vì tôi không muốn có được sự phân biệt tóc đó; như các poster ban đầu ghi chú, nhiều người thiếu tự tin khi nói đến việc đối phó với các đơn nguyên. Jargon-laden đặc trưng lý thuyết thể loại của các hoạt động đơn giản hoạt động chống lại việc phát triển một cảm giác tự tin và hiểu biết.
Eric Lippert

2
Vì vậy, lời khuyên của bạn là thay thế Nullbằng Nullable+ một số mã soạn sẵn? :)
Eric Duminil

3
@Claude: Bạn nên đọc hướng dẫn của tôi. Một đơn nguyên là một loại chung tuân theo các quy tắc nhất định và cung cấp khả năng liên kết với nhau một chuỗi các hoạt động, vì vậy trong trường hợp này, Measurement<T>là loại đơn nguyên.
Eric Lippert

5
@daboross: Mặc dù tôi đồng ý rằng các đơn vị nhà nước là một cách tốt để giới thiệu các đơn vị, tôi không nghĩ mang theo trạng thái là điều đặc trưng cho một đơn nguyên. Tôi nghĩ rằng thực tế là bạn có thể liên kết với nhau một chuỗi các chức năng là điều hấp dẫn; trạng thái chỉ là một chi tiết thực hiện.
Eric Lippert

18

Tôi nghĩ rằng trong trường hợp này, một biến thể trên Mẫu đối tượng Null sẽ hữu ích:

public class Measurement
{
    private int value;
    private bool isUnknown = false;
    private bool isMissing = false;

    private Measurement() { }
    public Measurement(int value) { this.value = value; }

    public int Value {
        get {
            if (!isUnknown && !isMissing)
            {
                return this.value;
            }
            throw new SomeException("...");
        }                   
    }

    public static readonly Measurement Unknown = new Measurement
    {
        isUnknown = true
    };

    public static readonly Measurement Missing = new Measurement
    {
        isMissing = true
    };
}

Bạn có thể biến nó thành một cấu trúc, ghi đè Equals / GetHashCode / ToString, thêm các chuyển đổi ngầm định từ hoặc sang intvà nếu bạn muốn hành vi giống NaN, bạn cũng có thể triển khai các toán tử số học của riêng mình, ví dụ như vậy. Measurement.Unknown * 2 == Measurement.Unknown.

Điều đó nói rằng, C # Nullable<int>thực hiện tất cả những điều đó, với sự cảnh báo duy nhất là bạn không thể phân biệt giữa các loại nulls khác nhau . Tôi không phải là người Java, nhưng tôi hiểu rằng Java OptionalIntlà tương tự và các ngôn ngữ khác có thể có các phương tiện riêng để đại diện cho một Optionalloại.


6
Việc thực hiện phổ biến nhất mà tôi đã thấy của mẫu này liên quan đến thừa kế. Có thể có một trường hợp cho hai lớp con: MissingMeasousing và UnknownMeasousing. Họ có thể thực hiện hoặc ghi đè các phương thức trong lớp Đo lường cha. +1
Greg Burghardt

2
Không phải là điểm của Mẫu đối tượng Null mà bạn không thất bại với các giá trị không hợp lệ, mà là không làm gì cả?
Chris Wohlert

2
@ChrisWohlert trong trường hợp này, đối tượng không thực sự có bất kỳ phương thức nào ngoại trừ Valuegetter, điều này hoàn toàn thất bại vì bạn không thể chuyển đổi Unknowntrở lại thành một int. Nếu phép đo có SaveToDatabase()phương thức, giả sử, thì việc triển khai tốt có thể sẽ không thực hiện giao dịch nếu đối tượng hiện tại là đối tượng null (thông qua so sánh với đơn lẻ hoặc ghi đè phương thức).
Maciej Stachowski

3
@MaciejStachowski Vâng, tôi không nói là không nên làm gì cả, tôi đang nói Mô hình đối tượng Null không phù hợp. Giải pháp của bạn có thể ổn, nhưng tôi sẽ không gọi nó là Mẫu đối tượng Null .
Chris Wohlert

14

Nếu bạn thực sự PHẢI sử dụng một số nguyên thì chỉ có một giải pháp khả thi. Sử dụng một số giá trị có thể là 'số ma thuật' có nghĩa là 'mất tích' và 'không xác định'

ví dụ: 2.147.483.647 và 2.147.483.646

Nếu bạn chỉ cần số đo int 'thực', thì hãy tạo cấu trúc dữ liệu phức tạp hơn

class Measurement {
    public bool IsEmpty;
    public bool IsKnown;
    public int Value {
        get {
            if(!IsEmpty && IsKnown) return _value;
            throw new Exception("NaN");
            }
        }
}

Làm rõ quan trọng:

Bạn có thể đạt được yêu cầu toán học bằng cách nạp chồng các toán tử cho lớp

public static Measurement operator+ (Measurement a, Measurement b) {
    if(a.IsEmpty) { return b; }
    ...etc
}

10
@KakturusOption<Option<Int>>
Bergi

5
@Bergi Bạn không thể nghĩ rằng điều đó thậm chí có thể chấp nhận được từ xa ..
BlueRaja - Danny Pflughoeft

8
@ BlueRaja-DannyPflughoeft Trên thực tế, nó phù hợp với mô tả của OP khá tốt, có cấu trúc lồng nhau. Tất nhiên, để trở nên chấp nhận được, chúng tôi sẽ giới thiệu một bí danh loại thích hợp (hoặc "newtype") - nhưng type Measurement = Option<Int>kết quả là số nguyên hoặc số đọc trống là ok, và vì vậy, Option<Measurement>một phép đo có thể được thực hiện hay không .
Bergi

7
@arp "Số nguyên gần NaN"? Bạn có thể giải thích những gì bạn có ý nghĩa bởi điều đó? Có vẻ hơi trái ngược khi nói rằng một con số "gần" chính khái niệm về một thứ không phải là một con số.
Nic Hartley

3
@Nic Hartley Trong hệ thống của chúng tôi, một nhóm những gì sẽ "tự nhiên" là số nguyên phủ định thấp nhất có thể được dành riêng là NaN. Chúng tôi đã sử dụng không gian đó để mã hóa các lý do khác nhau tại sao các byte đó đại diện cho thứ gì đó ngoài dữ liệu hợp pháp. (nó đã có từ nhiều thập kỷ trước và tôi có thể đã làm mờ một số chi tiết, nhưng chắc chắn có một tập hợp các bit bạn có thể đặt vào một giá trị số nguyên để khiến nó ném NaN nếu bạn cố gắng làm toán với nó.
arp

11

Nếu các biến của bạn được nổi điểm số, IEEE754 (tiêu chuẩn số dấu chấm động được hỗ trợ bởi hầu hết các bộ vi xử lý hiện đại và ngôn ngữ) có lưng: nó là một tính năng ít được biết đến, nhưng tiêu chuẩn xác định không phải một, nhưng một gia đình toàn bộ của Các giá trị NaN (không phải là số), có thể được sử dụng cho các ý nghĩa do ứng dụng xác định tùy ý. Chẳng hạn, trong các float có độ chính xác đơn, bạn có 22 bit miễn phí mà bạn có thể sử dụng để phân biệt giữa 2 ^ {22} loại giá trị không hợp lệ.

Thông thường, các giao diện lập trình chỉ hiển thị một trong số chúng (ví dụ: Numpy's nan); Tôi không biết có cách nào tích hợp để tạo ra các cách khác ngoài thao tác bit rõ ràng hay không, nhưng đó chỉ là vấn đề viết một vài thói quen cấp thấp. (Bạn cũng sẽ cần một cái để phân biệt chúng, bởi vì, theo thiết kế, a == bluôn trả về sai khi một trong số chúng là NaN.)

Sử dụng chúng tốt hơn là phát minh lại "số ma thuật" của riêng bạn để báo hiệu dữ liệu không hợp lệ, vì chúng truyền đúng và báo hiệu không hợp lệ: chẳng hạn, bạn không mạo hiểm tự bắn vào chân mình nếu bạn sử dụng average()chức năng và quên kiểm tra giá trị đặc biệt của bạn.

Rủi ro duy nhất là các thư viện không hỗ trợ chúng một cách chính xác, vì chúng là một tính năng khá tối nghĩa: ví dụ, một thư viện tuần tự hóa có thể 'làm phẳng' tất cả chúng giống nhau nan(có vẻ tương đương với hầu hết các mục đích).


6

Tiếp theo câu trả lời David Arno của , bạn có thể làm một cái gì đó giống như một sự kết hợp kỳ thị trong OOP, và trong một phong cách đối tượng chức năng chẳng hạn như tạo nên bởi Scala, bởi Java 8 loại chức năng, hoặc một thư viện Java FP như Vavr hoặc Fugue nó cảm thấy khá tự nhiên để viết một cái gì đó như:

var value = Measurement.of(2);
out.println(value.map(x -> x * 2));

var empty = Measurement.empty();
out.println(empty.map(x -> x * 2));

var unknown = Measurement.unknown();
out.println(unknown.map(x -> x * 2));

in ấn

Value(4)
Empty()
Unknown()

( Thực hiện đầy đủ như một ý chính .)

Ngôn ngữ hoặc thư viện FP cung cấp các công cụ khác như Try(aka Maybe) (một đối tượng chứa giá trị hoặc lỗi) và Either(đối tượng chứa giá trị thành công hoặc giá trị thất bại) cũng có thể được sử dụng ở đây.


2

Giải pháp lý tưởng cho vấn đề của bạn là xoay quanh lý do tại sao bạn quan tâm đến sự khác biệt giữa một thất bại đã biết và một phép đo không đáng tin cậy đã biết và những quy trình hạ nguồn mà bạn muốn hỗ trợ. Lưu ý, 'các quy trình hạ nguồn' trong trường hợp này không loại trừ các nhà khai thác con người hoặc các nhà phát triển đồng nghiệp.

Đơn giản chỉ cần đưa ra một "hương vị thứ hai" của null sẽ không cung cấp cho bộ quy trình xuôi dòng đủ thông tin để có được một tập hợp các hành vi hợp lý.

Thay vào đó, nếu bạn đang dựa vào các giả định theo ngữ cảnh về nguồn gốc của các hành vi xấu được tạo ra bởi mã hạ nguồn, tôi sẽ gọi đó là kiến ​​trúc xấu.

Nếu bạn biết đủ để phân biệt giữa lý do thất bại và thất bại mà không có lý do và thông tin đó sẽ thông báo cho các hành vi trong tương lai, bạn nên truyền đạt kiến ​​thức đó hoặc xử lý nội tuyến.

Một số mẫu để xử lý việc này:

  • Các loại tổng
  • Công đoàn phân biệt đối xử
  • Các đối tượng hoặc cấu trúc có chứa một enum đại diện cho kết quả của hoạt động và một trường cho kết quả
  • Chuỗi ma thuật hoặc số ma thuật không thể đạt được thông qua hoạt động bình thường
  • Ngoại lệ, trong các ngôn ngữ sử dụng này là thành ngữ
  • Nhận ra rằng thực sự không có bất kỳ giá trị nào trong việc phân biệt giữa hai kịch bản này và chỉ sử dụng null

2

Nếu tôi quan tâm đến việc "hoàn thành công việc" hơn là một giải pháp tao nhã, thì việc hack nhanh và bẩn sẽ chỉ đơn giản là sử dụng các chuỗi "không xác định", "thiếu" và 'biểu diễn chuỗi giá trị số của tôi', sau đó sẽ là chuyển đổi từ một chuỗi và được sử dụng khi cần thiết. Thực hiện nhanh hơn viết này, và trong ít nhất một số trường hợp, hoàn toàn đầy đủ. (Hiện tôi đang hình thành một nhóm cá cược về số lượng lượt tải xuống ...)


Được khuyến khích để đề cập đến "hoàn thành một cái gì đó."
Tạm biệt cô Chipps

4
Một số người có thể lưu ý rằng điều này gặp phải hầu hết các vấn đề tương tự như khi sử dụng NULL, cụ thể là nó chỉ chuyển từ cần kiểm tra NULL sang cần kiểm tra "không xác định" và "mất tích", nhưng vẫn giữ sự cố thời gian chạy vì lỗi dữ liệu im lặng, may mắn cho không may mắn là chỉ số duy nhất mà bạn quên kiểm tra. Ngay cả các kiểm tra NULL bị thiếu cũng có lợi thế là các linters có thể bắt được chúng, nhưng điều này làm mất điều đó. Tuy nhiên, nó có thêm sự phân biệt giữa "không xác định" và "mất tích", do đó, nó đánh bại NULL ở đó ...
8bittree

2

Ý chính nếu câu hỏi có vẻ là "Làm thế nào để tôi trả lại hai mẩu thông tin không liên quan từ một phương thức trả về một int? Tôi không bao giờ muốn kiểm tra giá trị trả về của mình và null là xấu, đừng sử dụng chúng."

Hãy nhìn vào những gì bạn muốn vượt qua. Bạn đang vượt qua một int hoặc một lý do không int cho lý do tại sao bạn không thể đưa ra int. Câu hỏi khẳng định rằng sẽ chỉ có hai lý do, nhưng bất kỳ ai đã từng làm enum đều biết rằng bất kỳ danh sách nào cũng sẽ phát triển. Phạm vi để xác định các lý do khác chỉ có ý nghĩa.

Ban đầu, sau đó, có vẻ như đây có thể là một trường hợp tốt để ném ngoại lệ.

Khi bạn muốn nói với người gọi một cái gì đó đặc biệt không thuộc kiểu trả về, ngoại lệ thường là hệ thống phù hợp: ngoại lệ không chỉ dành cho trạng thái lỗi và cho phép bạn trả lại nhiều ngữ cảnh và lý do để giải thích lý do tại sao bạn chỉ có thể hôm nay

Và đây là hệ thống DUY NHẤT cho phép bạn trả về các số nguyên có giá trị được bảo đảm và đảm bảo rằng mọi toán tử int và phương thức lấy int có thể chấp nhận giá trị trả về của phương thức này mà không cần phải kiểm tra các giá trị không hợp lệ như null hoặc giá trị ma thuật.

Nhưng các trường hợp ngoại lệ thực sự chỉ là một giải pháp hợp lệ nếu như tên của nó ngụ ý, đây là một trường hợp đặc biệt , không phải là quá trình kinh doanh thông thường.

Và một thử / bắt và xử lý cũng giống như một bản kiểm tra null, đó là những gì đã bị phản đối ở nơi đầu tiên.

Và nếu người gọi không chứa thử / bắt, thì người gọi phải gọi, v.v.


Một vượt qua thứ hai ngây thơ là để nói "Đó là một phép đo. Đo khoảng cách tiêu cực là không thể." Vì vậy, đối với một số phép đo Y, bạn chỉ có thể có hằng số cho

  • -1 = chưa biết,
  • -2 = không thể đo lường,
  • -3 = từ chối trả lời,
  • -4 = được biết nhưng bí mật,
  • -5 = thay đổi tùy theo pha mặt trăng, xem bảng 5a,
  • -6 = bốn chiều, các phép đo được đưa ra trong tiêu đề,
  • -7 = lỗi đọc hệ thống tệp,
  • -8 = dành riêng cho sử dụng trong tương lai,
  • -9 = hình vuông / khối nên Y giống với X,
  • -10 = là màn hình theo dõi nên không sử dụng các phép đo X, Y: sử dụng X làm đường chéo màn hình,
  • -11 = đã viết các số đo ở mặt sau của hóa đơn và nó đã được rửa thành bất hợp pháp nhưng tôi nghĩ rằng đó là 5 hoặc 17,
  • -12 = ... bạn có ý tưởng.

Đây là cách nó được thực hiện trong rất nhiều hệ thống C cũ và ngay cả trong các hệ thống hiện đại, nơi có một ràng buộc thực sự đối với int, và bạn không thể bọc nó thành một cấu trúc hoặc một loại nào đó.

Nếu các phép đo có thể âm tính, thì bạn chỉ cần làm cho kiểu dữ liệu của mình lớn hơn (ví dụ int dài) và có các giá trị ma thuật cao hơn phạm vi của int và lý tưởng bắt đầu bằng một số giá trị sẽ hiển thị rõ ràng trong trình gỡ lỗi.

Có nhiều lý do tốt để có chúng như một biến riêng biệt, thay vì chỉ có số ma thuật. Ví dụ, gõ nghiêm ngặt, bảo trì và phù hợp với mong đợi.


Trong nỗ lực thứ ba của chúng tôi, sau đó, chúng tôi xem xét các trường hợp đó là quá trình kinh doanh bình thường để có các giá trị không phải là int. Ví dụ: nếu một tập hợp các giá trị này có thể chứa nhiều mục không nguyên. Điều này có nghĩa là một xử lý ngoại lệ có thể là cách tiếp cận sai.

Trong trường hợp đó, nó có vẻ là một trường hợp tốt cho một cấu trúc vượt qua int và cơ sở lý luận. Một lần nữa, lý do này có thể chỉ là một const như trên, nhưng thay vì giữ cả hai trong cùng một int, bạn lưu trữ chúng như các phần riêng biệt của một cấu trúc. Ban đầu, chúng ta có quy tắc là nếu lý do được đặt, int sẽ không được đặt. Nhưng chúng ta không còn bị ràng buộc với quy tắc này; chúng tôi cũng có thể cung cấp các số liệu hợp lệ cho các số hợp lệ.

Dù bằng cách nào, mỗi khi bạn gọi nó, bạn vẫn cần nồi hơi, để kiểm tra lý do để xem liệu int có hợp lệ hay không, sau đó rút ra và sử dụng phần int nếu lý do cho phép chúng tôi.

Đây là nơi bạn cần điều tra lý do của mình đằng sau "không sử dụng null".

Giống như các trường hợp ngoại lệ, null có nghĩa là để biểu thị một trạng thái đặc biệt.

Nếu một người gọi đang gọi phương thức này và bỏ qua hoàn toàn phần "cơ sở" của cấu trúc, mong đợi một số mà không có bất kỳ xử lý lỗi nào và nó nhận được số 0, thì nó sẽ xử lý số 0 dưới dạng số và sai. Nếu nó có được một con số ma thuật, nó sẽ coi đó là một con số và sai. Nhưng nếu nó bị vô hiệu, nó sẽ rơi xuống , vì nó cũng nên làm.

Vì vậy, mỗi khi bạn gọi phương thức này, bạn phải kiểm tra giá trị trả về của nó, tuy nhiên bạn xử lý các giá trị không hợp lệ, cho dù trong băng tần hay ngoài băng, thử / bắt, kiểm tra cấu trúc cho thành phần "hợp lý", kiểm tra int để biết số ma thuật hoặc kiểm tra int cho null ...

Cách khác, để xử lý phép nhân của một đầu ra có thể chứa một int không hợp lệ và một lý do hợp lý như "Con chó của tôi đã ăn phép đo này", là làm quá tải toán tử nhân cho cấu trúc đó.

... Và sau đó quá tải mọi nhà khai thác khác trong ứng dụng của bạn có thể được áp dụng cho dữ liệu này.

... Và sau đó quá tải tất cả các phương thức có thể mất ints.

... Và tất cả những quá tải sẽ cần phải vẫn chứa kiểm tra cho ints không hợp lệ, chỉ để bạn có thể đối xử với các kiểu trả về của một phương pháp này như thể nó là luôn luôn là một int có giá trị tại thời điểm khi bạn đang gọi nó.

Vì vậy, tiền đề ban đầu là sai theo nhiều cách khác nhau:

  1. Nếu bạn có các giá trị không hợp lệ, bạn không thể tránh việc kiểm tra các giá trị không hợp lệ đó tại bất kỳ điểm nào trong mã nơi bạn đang xử lý các giá trị.
  2. Nếu bạn trả lại bất cứ thứ gì ngoài int, bạn sẽ không trả về int, vì vậy bạn không thể coi nó như int. Quá tải toán tử cho phép bạn giả vờ , nhưng đó chỉ là giả vờ.
  3. Một int có số ma thuật (bao gồm NULL, NAN, Inf ...) không còn thực sự là int, đó là cấu trúc của một người nghèo.
  4. Tránh null sẽ không làm cho mã mạnh hơn, nó sẽ chỉ che giấu các vấn đề với ints hoặc chuyển chúng vào một cấu trúc xử lý ngoại lệ phức tạp.

1

Tôi không hiểu tiền đề của câu hỏi của bạn, nhưng đây là câu trả lời mệnh giá. Đối với thiếu hoặc trống, bạn có thể làm math.nan(Không phải là số). Bạn có thể thực hiện bất kỳ hoạt động toán học trên math.nanvà nó sẽ vẫn còn math.nan.

Bạn có thể sử dụng None(null của Python) cho một giá trị không xác định. Dù sao bạn cũng không nên thao túng một giá trị không xác định và một số ngôn ngữ (Python không phải là một trong số chúng) có các toán tử null đặc biệt để thao tác chỉ được thực hiện nếu giá trị không trống, nếu không giá trị vẫn là null.

Các ngôn ngữ khác có các mệnh đề bảo vệ (như Swift hoặc Ruby) và Ruby có sự trở lại sớm có điều kiện.

Tôi đã thấy điều này được giải quyết bằng Python theo một số cách khác nhau:

  • với cấu trúc dữ liệu bao bọc, vì thông tin số thường là về một thực thể và có thời gian đo. Trình bao bọc có thể ghi đè các phương thức ma thuật như thế __mult__để không có ngoại lệ nào được đưa ra khi các giá trị Không xác định hoặc Mất tích của bạn xuất hiện. Numpy và gấu trúc có thể có khả năng như vậy trong đó.
  • với giá trị sentinel (như của bạn Unknownhoặc -1 / -2) và câu lệnh if
  • với một cờ boolean riêng
  • với cấu trúc dữ liệu lười biếng - chức năng của bạn thực hiện một số thao tác trên cấu trúc, sau đó nó trả về, chức năng ngoài cùng cần kết quả thực tế đánh giá cấu trúc dữ liệu lười biếng
  • với một đường dẫn hoạt động lười biếng - tương tự như hoạt động trước đó, nhưng hoạt động này có thể được sử dụng trên một tập hợp dữ liệu hoặc cơ sở dữ liệu

1

Làm thế nào giá trị được lưu trữ trong bộ nhớ phụ thuộc vào ngôn ngữ và chi tiết thực hiện. Tôi nghĩ những gì bạn có ý nghĩa là cách đối tượng nên cư xử với lập trình viên. (Đây là cách tôi đọc câu hỏi, cho tôi biết nếu tôi sai.)

Bạn đã đề xuất một câu trả lời cho câu hỏi đó rồi: sử dụng lớp của riêng bạn chấp nhận mọi hoạt động toán học và tự trả về mà không đưa ra một ngoại lệ. Bạn nói rằng bạn muốn điều này bởi vì bạn muốn tránh kiểm tra null.

Giải pháp 1: không tránh kiểm tra null

Missingcó thể được đại diện như math.nan
Unknowncó thể được đại diện nhưNone

Nếu bạn có nhiều hơn một giá trị, bạn chỉ có thể filter()áp dụng thao tác trên các giá trị không Unknownhoặc Missinghoặc bất kỳ giá trị nào bạn muốn bỏ qua cho hàm.

Tôi không thể tưởng tượng ra một kịch bản mà bạn cần kiểm tra null trên một hàm hoạt động trên một vô hướng. Trong trường hợp đó, thật tốt khi buộc kiểm tra null.


Giải pháp 2: sử dụng một công cụ trang trí bắt ngoại lệ

Trong trường hợp này, Missingcó thể tăng MissingExceptionUnknowncó thể tăng UnknownExceptionkhi các thao tác được thực hiện trên nó.

@suppressUnknown(value=Unknown) # if an UnknownException is raised, return this value instead
@suppressMissing(value=Missing)
def sigmoid(value):
    ...

Ưu điểm của phương pháp này là các thuộc tính của MissingUnknownchỉ bị triệt tiêu khi bạn yêu cầu rõ ràng yêu cầu chúng bị triệt tiêu. Một ưu điểm khác là cách tiếp cận này là tự ghi lại tài liệu: mọi chức năng cho thấy liệu nó có mong đợi một điều chưa biết hoặc thiếu và cách thức chức năng.

Khi bạn gọi một chức năng không mong đợi Mất tích sẽ bị mất, chức năng sẽ tăng ngay lập tức, cho bạn biết chính xác nơi xảy ra lỗi thay vì âm thầm thất bại và tuyên truyền Mất chuỗi cuộc gọi. Điều tương tự cũng xảy ra với Unknown.

sigmoidvẫn có thể gọi sin, mặc dù nó không mong đợi một Missinghoặc Unknown, vì sigmoidngười trang trí sẽ bắt ngoại lệ.


1
tự hỏi ý nghĩa của việc đăng hai câu trả lời cho cùng một câu hỏi (đây là câu trả lời trước của bạn , có gì sai với nó không?)
gnat

@gnat Câu trả lời này cung cấp lý do tại sao không nên thực hiện theo cách mà tác giả thể hiện và tôi không muốn gặp rắc rối khi tích hợp hai câu trả lời với các ý tưởng khác nhau - thật dễ dàng để viết hai câu trả lời có thể đọc độc lập . Tôi không hiểu tại sao bạn quan tâm nhiều đến lý do vô hại của người khác.
noɥʇʎԀʎzɐɹƆ

0

Giả sử tìm nạp số lượng CPU trong một máy chủ. Nếu máy chủ bị tắt hoặc đã bị loại bỏ, giá trị đó đơn giản là không tồn tại. Đây sẽ là một phép đo không có ý nghĩa gì (có thể "thiếu" / "trống" không phải là thuật ngữ tốt nhất). Nhưng giá trị được "biết" là vô nghĩa. Nếu máy chủ tồn tại, nhưng quá trình tìm nạp giá trị gặp sự cố, đo nó là hợp lệ, nhưng không thành công dẫn đến giá trị "không xác định".

Cả hai điều này nghe giống như điều kiện lỗi, vì vậy tôi sẽ đánh giá rằng lựa chọn tốt nhất ở đây là chỉ cần get_measurement()ném cả hai điều này thành ngoại lệ ngay lập tức (chẳng hạn như DataSourceUnavailableException, hoặc SpectacularFailureToGetDataException, tương ứng). Sau đó, nếu có bất kỳ sự cố nào xảy ra, mã thu thập dữ liệu có thể phản ứng ngay lập tức (chẳng hạn như thử lại trong trường hợp sau) và get_measurement()chỉ phải trả lại inttrong trường hợp có thể lấy dữ liệu từ dữ liệu thành công nguồn - và bạn biết rằng đó intlà hợp lệ.

Nếu tình huống của bạn không hỗ trợ các trường hợp ngoại lệ hoặc không thể sử dụng chúng nhiều, thì một cách thay thế tốt là sử dụng mã lỗi, có thể được trả lại thông qua một đầu ra riêng biệt get_measurement(). Đây là mẫu thành ngữ trong C, trong đó đầu ra thực tế được lưu trữ trong một con trỏ đầu vào và mã lỗi được truyền lại dưới dạng giá trị trả về.


0

Các câu trả lời đã cho là tốt, nhưng vẫn không phản ánh mối quan hệ phân cấp giữa giá trị, trống rỗng và không xác định.

  • Cao nhất đến không rõ .
  • Sau đó, trước khi sử dụng một giá trị trống đầu tiên phải được làm rõ.
  • Cuối cùng đến giá trị để tính toán với.

Xấu xí (vì sự trừu tượng không thành công), nhưng hoạt động đầy đủ sẽ là (bằng Java):

Optional<Optional<Integer>> unknowableValue;

unknowableValue.ifPresent(emptiableValue -> ...);
Optional<Integer> emptiableValue = unknowableValue.orElse(Optional.empty());

emptiableValue.ifPresent(value -> ...);
int value = emptiableValue.orElse(0);

Ở đây các ngôn ngữ chức năng với một hệ thống loại tốt là tốt hơn.

Trong thực tế: Cácgiá trị trống / thiếu không xác định * dường như là một phần của trạng thái quá trình, một số đường ống sản xuất. Giống như Excel trải rộng các ô với công thức tham chiếu các ô khác. Ở đó người ta sẽ nghĩ về việc có thể lưu trữ lambdas theo ngữ cảnh. Thay đổi một ô sẽ đánh giá lại tất cả các ô phụ thuộc đệ quy.

Trong trường hợp đó, giá trị int sẽ được nhận bởi nhà cung cấp int. Một giá trị trống sẽ cung cấp cho một nhà cung cấp int ném một ngoại lệ trống hoặc đánh giá để trống (đệ quy trở lên). Công thức chính của bạn sẽ kết nối tất cả các giá trị và có thể trả về một giá trị trống (giá trị / ngoại lệ). Một giá trị không xác định sẽ vô hiệu hóa đánh giá bằng cách ném một ngoại lệ.

Các giá trị có thể có thể quan sát được, giống như một thuộc tính ràng buộc java, thông báo cho người nghe về sự thay đổi.

Tóm lại: Mẫu định kỳ của các giá trị cần với các trạng thái bổ sung trống và không xác định dường như chỉ ra rằng một bảng tính trải rộng hơn như mô hình dữ liệu thuộc tính ràng buộc có thể tốt hơn.


0

Có, khái niệm về nhiều loại NA khác nhau tồn tại trong một số ngôn ngữ; nhiều hơn trong các thống kê, nơi nó có ý nghĩa hơn (viz. sự khác biệt lớn giữa Mất tích ngẫu nhiên, Mất tích-Hoàn toàn-Ngẫu nhiên, Mất tích-Không-Ngẫu nhiên ).

  • nếu chúng ta chỉ đo chiều dài của widget, thì việc phân biệt giữa 'lỗi cảm biến' hoặc 'cắt điện' hoặc 'lỗi mạng' (mặc dù 'tràn số' không truyền tải thông tin)

  • nhưng trong ví dụ khai thác dữ liệu hoặc khảo sát, hỏi người trả lời ví dụ như thu nhập hoặc tình trạng HIV của họ, kết quả của 'Không xác định' khác với 'Từ chối trả lời', và bạn có thể thấy rằng các giả định trước của chúng tôi về cách áp đặt sau này sẽ có xu hướng để khác với trước đây. Vì vậy, các ngôn ngữ như SAS hỗ trợ nhiều loại NA khác nhau; ngôn ngữ R không có nhưng người dùng thường phải hack xung quanh đó; NA tại các điểm khác nhau trong một đường ống có thể được sử dụng để biểu thị những điều rất khác nhau.

  • cũng có trường hợp chúng ta có nhiều biến NA cho một mục nhập ("nhiều lần cắt ngang"). Ví dụ: nếu tôi không biết bất kỳ độ tuổi, mã zip, trình độ học vấn hoặc thu nhập nào của một người, thì việc thu nhập của họ sẽ khó hơn.

Về cách bạn đại diện cho các loại NA khác nhau trong các ngôn ngữ có mục đích chung không hỗ trợ chúng, thông thường mọi người sẽ hack những thứ như dấu phẩy động (yêu cầu chuyển đổi số nguyên), enums hoặc sentinels (ví dụ 999 hoặc -1000) cho số nguyên hoặc giá trị phân loại. Thông thường, không có câu trả lời rất rõ ràng, xin lỗi.


0

R có tích hợp hỗ trợ giá trị thiếu. https://medium.com/coinmonks/deals-with-missing-data-USE-r-3ae428da2d17

Chỉnh sửa: bởi vì tôi đã bị đánh giá thấp, tôi sẽ giải thích một chút.

Nếu bạn định xử lý số liệu thống kê, tôi khuyên bạn nên sử dụng ngôn ngữ thống kê như R vì R được các nhà thống kê viết cho các nhà thống kê. Thiếu giá trị là một chủ đề lớn đến nỗi họ dạy cho bạn cả một học kỳ. Và có những cuốn sách lớn chỉ về những giá trị còn thiếu.

Tuy nhiên, bạn có thể muốn đánh dấu bạn thiếu dữ liệu, như dấu chấm hoặc "thiếu" hoặc bất cứ điều gì. Trong R bạn có thể định nghĩa những gì bạn có nghĩa là thiếu. Bạn không cần phải chuyển đổi chúng.

Cách thông thường để xác định giá trị thiếu là đánh dấu chúng là NA.

x <- c(1, 2, NA, 4, "")

Sau đó, bạn có thể thấy những giá trị còn thiếu;

is.na(x)

Và sau đó kết quả sẽ là;

FALSE FALSE  TRUE FALSE FALSE

Như bạn có thể thấy ""là không thiếu. Bạn có thể đe dọa ""như chưa biết. Và NAđang mất tích.


@Hulk, ngôn ngữ chức năng nào khác hỗ trợ các giá trị còn thiếu? Ngay cả khi chúng hỗ trợ các giá trị bị thiếu, tôi chắc chắn rằng bạn không thể điền chúng bằng các phương pháp thống kê chỉ trong một dòng mã.
ilhan

-1

Có một lý do mà chức năng của các *nhà điều hành có thể được thay đổi thay thế?

Hầu hết các câu trả lời liên quan đến một giá trị tra cứu nào đó, nhưng có thể dễ dàng sửa đổi toán tử toán học hơn trong trường hợp này.

Sau đó, bạn có thể có chức năng tương tự empty()/ unknown()trên toàn bộ dự án của bạn.


4
Điều này có nghĩa là bạn sẽ phải quá tải tất cả các nhà khai thác
đường ống
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.