paint-brush
Repensando os anos bissextos: por que a abordagem da sua linguagem de programação favorita pode ser falhapor@mcsee
998 leituras
998 leituras

Repensando os anos bissextos: por que a abordagem da sua linguagem de programação favorita pode ser falha

por Maximiliano Contieri5m2024/02/29
Read on Terminal Reader

Muito longo; Para ler

A maioria das linguagens não consegue encontrar o comportamento correto para o cálculo do ano bissexto
featured image - Repensando os anos bissextos: por que a abordagem da sua linguagem de programação favorita pode ser falha
Maximiliano Contieri HackerNoon profile picture
0-item

Um erro histórico e como você pode resolvê-lo


TL;DR: A maioria das linguagens não consegue encontrar o comportamento correto para o cálculo do ano bissexto.


Isenção de responsabilidade: embora eu tenha tentado o meu melhor para fornecer insights precisos em várias linguagens de programação, reconheço que posso não ser um especialista em todas. Se você detectar um erro ou discordar de algum ponto, deixe um comentário respeitoso e eu resolverei o problema imediatamente.

O Estado da arte

Determinar se um ano é bissexto (ou não) é um problema matemático simples.

Cada aluno pode resolvê-lo como sua primeira tarefa de programação.

Para simplificar o problema, vamos supor que um ano seja bissexto quando for divisível por 4, exceto se também for divisível por 100, mas será um ano bissexto se for divisível por 400.

O mundo real e a mecânica cósmica são um pouco mais complicados, mas isso está além do escopo deste artigo.

Vamos explorar como diversas linguagens de programação resolvem esse problema:

Abordagem horrível

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;

Essas linguagens tentam criar um dia bissexto válido (ou inválido) e explorar valores verdadeiros .

Este hack viola o princípio do fracasso rápido e abusa do erro de um bilhão de dólares .

Tentar criar uma data inválida deve gerar uma exceção em linguagens sérias, pois isso acontece no domínio do mundo real .

Realizar outras ações, como ocultar erros sob a superfície, viola o princípio do menor espanto.

Comportamento ausente

Ada:

 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;

C/C++:

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

Ir:

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

Haskel:

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

JavaScript/TypeScript:

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

Júlia:

 using Dates year = 2024 isleap(year)

Lua:

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

MATLAB:

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

Objetivo-C:

 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)

Ferrugem:

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

Conversa fiada:

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

Os idiomas acima não oferecem suporte nativo.

Você precisa definir funções globais ou usar helpers .

Abordagem Global Incorreta

PHP (de novo):

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

R:

 leap_year(2024)

Rubi:

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

Rápido:

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

Essas linguagens usam funções globais para verificar se um ano é bissexto.

Esses métodos globais de utilidade colocam erroneamente a responsabilidade no local errado (um ponto de acesso global).

Abordagem ruim dos ajudantes

C#:

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

Dardo:

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

Perl:

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

Pitão:

 import calendar leap = calendar.isleap(2024)

Visual Básico.NET:

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

Essas linguagens usam auxiliares como bibliotecas para verificar se um ano é bissexto.

O extravio responsável não está presente em um objeto real, mas em um pacote de funções relacionadas ao DateTime .

A abordagem do ano

Java:

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

Kotlin:

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

Escala:

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

Essas línguas contam com o Ano para verificar se é um salto.

O protocolo está mais próximo do mundo real na bijeção

Observe que eles criam objetos Year e não objetos Integer , pois isso também quebraria a bijeção .

Um Year tem um protocolo diferente de um inteiro, e modelar um Year como um inteiro também seria um cheiro de otimização prematuro e um sintoma de mistura de what e how .

Um Year pode dizer se é um salto (um número inteiro não deveria fazer isso) e pode informar sobre seus meses (que são Months , não números inteiros baseados em 0 , números inteiros baseados em 1 ou strings).

Por outro lado, as capacidades de um Integer se estendem a operações aritméticas como multiplicação e exponenciação.

O tempo não é uma piada

Representar um momento no tempo como float , inteiro ou qualquer outro tipo de dados traz consequências.

Você pode quebrar um ponto no tempo no mundo real em pequenas frações (mas não muito pequenas )

Usar floats não é uma opção válida.

0,01 + 0,02 não é 0,03, e isso tem consequências terríveis ao lidar com pontos flutuantes no tempo.

O desafio

Estamos falando de anos bissextos.

Quais são as necessidades para saber se um ano é bissexto?

A mecânica de data e hora que você modela precisa saber o sucessor de 28 de fevereiro de 2024.

Mas este NÃO é problema seu.

Seguindo o princípio de ocultação de informações, você deve deixar a responsabilidade como um protocolo privado.

Conclusão

Não há bala de prata .

Use sua linguagem com sabedoria.

Hoje é 29 de fevereiro, um dia bissexto para fazer uma pausa e refletir sobre as ferramentas que você usa diariamente.

Vejo você em 4 anos.