Comment améliorer la qualité du code Java en utilisant notre traducteur

Nous allons parler des approches et des constructions de langage en C# : celles qui sont bonnes à utiliser et celles qui ne le sont pas. Bien sûr, par bon ou pas bon, nous considérons ce qui suit : à quel point le code Java résultant sera lisible et maintenable après la traduction du C#.

Compact en C# – volumineux en Java

Le C# possède certaines constructions de langage compactes qui font beaucoup de travail caché. Lorsque vous traduisez ces constructions dans une autre langue, vous devez implicitement reproduire cette partie cachée, et, dans la plupart des cas, le code perd son design original et diffère beaucoup.

Propriétés automatiques

Les propriétés automatiques sont très répandues parmi les programmeurs C#. En utilisant ce type de propriété, le programmeur peut interagir avec le champ caché via la méthode get et/ou set. Le C# nous permet de nous distraire de l'implémentation réelle des propriétés automatiques et d'utiliser des constructions très compactes pour les déclarer. Mais, en Java, nous n'avons pas une telle construction de langage, et il devient nécessaire de déclarer les propriétés explicitement : comme champ et méthodes de contrôle d'accès :

public int Value { get; set; }

En Java, cela devient :

private int auto_Value;
public int get_Value()
{
    return auto_Value;
}
public void set_Value(int value)
{
    auto_Value = value;
}

Maintenant, ce n'est pas un seul membre solide, il y a trois membres séparés. Imaginez le même code répété pour chaque propriété automatique, à quoi ressemblerait-il ? Évitons de telles expériences désagréables. Mais comment ?
Essayez de remplacer la propriété automatique par un objet, qui fournit un contrôle d'accès sur le champ privé. Il peut y avoir une table de hachage de tels objets. Si nous suivons une conception d'accès limité à certaines données, ce serait une bonne manière. Les propriétés automatiques peuvent paraître agréables, mais nous n'avons pas à les utiliser sans nécessité.

Types de valeur

En C#, nous avons une logique de mémoire dédiée pour les structures (types de valeur). Leur durée de vie est limitée par la durée de vie du cadre de pile ou de l'objet contenant, et ils sont fréquemment copiés – lorsque nous les passons comme arguments de fonction, les retournons d'une fonction, ou les assignons à un champ, nous opérons avec la valeur, pas la référence. En changeant la copie, nous ne changeons pas l'original. En traduisant les types de valeur en Java, nous devons recréer la même logique, même si les classes Java sont toujours des types de référence. La copie fréquente devient maintenant un problème – pour stocker chaque copie, nous allouons de la mémoire à partir du tas, surchargeant le ramasse-miettes. Si nous considérons la performance comme l'un de nos intérêts, nous devons abstraire notre code C# des détails de la gestion de la mémoire. Mais comment ?
La façon la plus simple de faire cela – rendre vos types de valeur immuables. Lorsque vous n'avez pas d'état modifiable, vous n'avez pas besoin de copier cet état pour prévenir un comportement indéterminé.

Constructions uniquement syntaxiques

Il est maintenant temps de parler des constructions de langage qui changent seulement les propriétés visuelles du code, mais pas le comportement. Par exemple :

public class Item
{
    string name;
    string price;
    
    public Item(string name, int price) => (this.name, this.price) = (name, price);
    
    public string ToString() => $"Name = {name}, Price = {price}";
    public string Name => name;
    public int Price => price;
}

Nous voyons ici la déconstruction de tuple (cette expression ne crée pas vraiment un tuple), un littéral de chaîne interpolée, des méthodes et des propriétés à expression.

Délégués

Les délégués sont bons à utiliser car c'est une forme courte de déclaration de méthode. Regardons l'exemple :

using System;
using System.Linq;

class Program
{
    delegate int ChangeNumber(int arg);
	static void Main()
	{
	    Console.WriteLine("Input some numbers");
        int[] numbers = Console.ReadLine().Split(" ").Select(int.Parse).ToArray();
        Console.WriteLine("Input addition");
        int addition = int.Parse(Console.ReadLine());
        ChangeNumbers(n => n + addition, numbers);
        Console.WriteLine("Result :");
        Console.WriteLine(string.Join(" ", numbers.Select(n => n.ToString())));
	}
	static void ChangeNumbers(ChangeNumber change, int[] numbers)
	{
	    for(int i = 0; i < numbers.Length; i++)
	    {
	        numbers[i] = change(numbers[i]);
	    }
	}
}

Pour cette expression n => n + addition, nous pouvons générer une expression de classe anonyme Java :

// translated to Java code
interface ChangeNumber
{
    int invoke(int arg);
}
// ...static void main(String[] args)...

// anonymous class expression for Java 7 or older version
changeNumbers(new ChangeNumber()
{
    public int invoke(int n)
    {
        return n + addition;
    }
}, numbers);

// or lambda expression for higher Java 8 or newer version
changeNumbers(n -> n + addition, numbers);

En conclusion

Le C# possède de nombreuses constructions de langage, qui peuvent rendre notre code simple, en cachant une grande mise en œuvre derrière du sucre syntaxique. Certaines de ces constructions sont discutables et difficilement supportables, d'autres sont flexibles et facilement reproductibles. En matière de conception, il est préférable de composer des abstractions par des objets, et non par des constructions de langage dédiées au C#. En matière de performance, nous devrions garder une abstraction de la gestion de la mémoire, nous libérant ainsi de l'émuler à double coût.

Articles liés :