paint-brush
Переосмысление високосных лет: почему подход вашего любимого языка программирования может быть ошибочнымк@mcsee
1,059 чтения
1,059 чтения

Переосмысление високосных лет: почему подход вашего любимого языка программирования может быть ошибочным

к Maximiliano Contieri5m2024/02/29
Read on Terminal Reader

Слишком долго; Читать

В большинстве языков не удается найти правильное поведение при расчете високосного года.
featured image - Переосмысление високосных лет: почему подход вашего любимого языка программирования может быть ошибочным
Maximiliano Contieri HackerNoon profile picture
0-item

Историческая ошибка и как ее исправить


TL;DR: Большинство языков не могут найти правильное поведение при вычислении високосного года.


Отказ от ответственности: хотя я изо всех сил старался предоставить точную информацию о различных языках программирования, я признаю, что не могу быть экспертом во всех. Если вы заметили ошибку или не согласны с каким-либо пунктом, оставьте уважительный комментарий, и я оперативно его исправлю.

Современное состояние

Определить, является ли год високосным (или нет), представляет собой простую математическую задачу.

Каждый студент может решить ее в качестве своего первого задания по программированию.

Чтобы упростить задачу, предположим, что год является високосным , если он делится на 4 без остатка, за исключением случаев, когда он также делится на 100, но високосным является год, если он делится на 400.

Реальный мир и космическая механика немного сложнее, но это выходит за рамки данной статьи.

Давайте рассмотрим, как несколько языков программирования решают эту проблему:

Ужасный подход

PHP:

 <?php $yearNumber = 2024; $isLeap = date('L', mktime(0, 0, 0, 1, 1, $yearNumber));

SQL (PostgreSQL):

 SELECT (EXTRACT(year FROM TIMESTAMP '2024-02-29') IS NOT NULL) AS is_leap_year;

Эти языки пытаются создать действительный (или недопустимый) високосный день и использовать истинные значения .

Этот хак нарушает принцип отказоустойчивости и злоупотребляет ошибкой на миллиард долларов .

Попытка создать неверную дату должна вызвать исключение в серьезных языках, поскольку это происходит в реальном мире .

Выполнение других действий, например сокрытие ошибок, нарушает принцип наименьшего удивления.

Отсутствующее поведение

Ада:

 function Is_Leap_Year (Year : Integer) return Boolean is begin return (Year mod 4 = 0 and then Year mod 100 /= 0) or else (Year mod 400 = 0); end Is_Leap_Year;

Си/С++:

 bool isLeapYear(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); }

Идти:

 package main import ( "fmt" "time" ) func isLeapYear(year int) bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) }

Хаскелл:

 import Data.Time.Calendar (isLeapYear) let year = 2024 let isLeap = isLeapYear year

JavaScript/Типскрипт:

 function isLeapYear(year) { return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0); }

Юлия:

 using Dates year = 2024 isleap(year)

Луа:

 local year = 2024 local isLeap = (year % 4 == 0 and year % 100 ~= 0) or (year % 400 == 0)

МАТЛАБ:

 year = 2024; isLeap = mod(year, 4) == 0 && (mod(year, 100) ~= 0 || mod(year, 400) == 0);

Цель-С:

 int yearNumber = 2024; BOOL isLeap = (yearNumber % 4 == 0 && yearNumber % 100 != 0) || (yearNumber % 400 == 0);

PowerShell:

 $yearNumber = 2024 $isLeap = ($yearNumber % 4 -eq 0 -and $yearNumber % 100 -ne 0) -or ($yearNumber % 400 -eq 0)

Ржавчина:

 fn is_leap_year(year: i32) -> bool { (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) }

Болтовня:

 | yearNumber | yearNumber := 2024. (yearNumber \\ 4 = 0) and: [(yearNumber \\ 100 ~= 0) or: [ yearNumber \\ 400 = 0 ]]

Вышеупомянутые языки не имеют встроенной поддержки.

Вам необходимо определить глобальные функции или использовать помощники .

Неправильный глобальный подход

PHP (снова):

 <?php $yearNumber = 2024; $isLeap = checkdate(2, 29, $yearNumber);

Р:

 leap_year(2024)

Рубин:

 year = 2024 is_leap = Date.leap?(year)

Быстрый:

 let yearNumber = 2024 let isLeap = Calendar.current.isDateInLeapYear( Date(timeIntervalSince1970: TimeInterval(yearNumber)))

Эти языки используют глобальные функции , чтобы проверить, является ли год високосным.

Эти служебные глобальные методы ошибочно переносят ответственность не в то место (глобальную точку доступа).

Помощники плохой подход

С#:

 int yearNumber = 2024; bool isLeap = System.DateTime.IsLeapYear(yearNumber);

Дарт:

 import 'package:intl/intl.dart'; var year = 2024; var isLeap = DateTime(year).isLeapYear;

Перл:

 use Time::Piece; my $yearNumber = 2024; my $isLeap = Time::Piece ->strptime("$yearNumber-01-01", "%Y-%m-%d")->leapyear;

Питон:

 import calendar leap = calendar.isleap(2024)

Визуал Бейсик .NET:

 Dim year As Integer = 2024 Dim isLeap As Boolean = DateTime.IsLeapYear(year)

Эти языки используют помощники в качестве библиотек, чтобы проверить, является ли год високосным.

Неуместная ответственность присутствует не в реальном объекте, а в наборе функций, связанных с DateTime .

Годовой подход

Джава:

 int yearNumber = 2024; boolean isLeap = java.time.Year.of(yearNumber).isLeap();

Котлин:

 val yearNumber = 2024 val isLeap = java.time.Year.of(yearNumber).isLeap

Скала:

 val year = 2024 val isLeap = java.time.Year.of(year).isLeap

Эти языки полагаются на год , чтобы проверить, является ли он скачком.

Протокол ближе к реальному миру в биекции

Обратите внимание, что они создают объекты Year , а не объекты Integer , поскольку это также нарушит биекцию .

У Года другой протокол, чем у целого числа, и моделирование Года как целого числа также было бы запахом преждевременной оптимизации и признаком смешивания того, что и как .

Год может определить, является ли это скачком (целое число не должно этого делать), и может рассказать вам о своих месяцах (которые представляют собой Months , а не целые числа, отсчитываемые от 0 , целые числа, отсчитываемые от 1, или строки).

И наоборот, возможности Integer распространяются на арифметические операции, такие как умножение и возведение в степень.

Время – это не шутка

Представление момента времени в виде числа с плавающей запятой , целого числа или любого другого типа данных имеет последствия.

Вы можете разбить момент времени в реальном мире на крошечные доли (но не слишком маленькие ).

Использование чисел с плавающей запятой не является допустимым вариантом.

0,01 + 0,02 — это не 0,03, и это имеет ужасные последствия , связанные с точками с плавающей запятой во времени.

Соревнование

Мы говорили о високосных годах.

Что нужно знать, если год високосный?

Механика даты и времени, которую вы моделируете, должна знать преемник от 28 февраля 2024 года.

Но это НЕ ваша проблема.

Следуя принципу сокрытия информации, вам следует оставить ответственность в виде частного протокола.

Заключение

Серебряной пули не существует.

Используйте свой язык с умом.

Сегодня 29 февраля, високосный день, чтобы сделать паузу и поразмышлять над инструментами, которые вы используете ежедневно.

Увидимся через 4 года.