15 March 2024

How to Improve Java Code Quality When Using Our Translator

We are going to talk about approaches and language constructs in C#: which are good to use and which are not good. Of course, under good or not good we consider the following: how readable and maintainable will be resulting Java code after translation from C#.

Compact in C# – large in Java

C# has some compact language constructs that do a lot of hidden work. When you are translating those constructs to another language, you have to implicitly reproduce that hidden part, and, in most cases, the code does lose its original design and differs very much.

Auto-properties

Auto-properties have very spread use among C# programmers. Using that kind of property programmer can interact with the hidden field through the get and/or set method. C# does let us distract from the actual implementation of the auto-properties and use very compact constructs to declare them. But, in Java, we don't have such language construct, and there comes necessary to declare properties explicitly: as field and access control methods :

public int Value { get; set; }

In Java becomes :

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

Now it is not one solid member, there are three separate members. Imagine the same code repeated for each auto-property, what would it look like? Let us avoid such unpleasant experiences. But how?
Try to replace auto-property with an object, that does provide access control over the private field. There may be a hash map of such objects. If we are following a design of limited access to some data, it would be a good way. Auto-properties may look nice, but we don't have to use them with no necessity.

Value types

In C# we have a dedicated memory logic for structs (value types). Their lifetime is limited by the lifetime of the stack frame or containing object, and they are being copied frequently – when we pass them as function arguments, return from a function, or assign to some field, we operate with value, not reference. Changing copy, we do not change the original. Translating value types to Java, we have to recreate the same logic, even though Java classes are always reference types. Frequent copying becomes a problem now – to store each copy we allocate memory from the heap, overloading the garbage collector. If we do consider performance as one of our interests, we have to abstract our C# code from memory management details. But how?
The easiest way to do that – make your value types immutable. When you have no mutable state, you have no necessary to copy that state preventing undetermined behavior.

Syntax-only constructs

Now is a time to talk about language constructs that change only visual properties of code, but not behavior. For example :

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;
}

We see here tuple deconstruction (that expression does not create a tuple really), interpolated string literal, expression-bodied methods and properties.

Delegates

Delegates are good to use because it is a short form of method declaration. Let us look at the example :

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]);
	    }
	}
}

For that n => n + addition expression we may generate Java anonymous class expression :

// 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);

Coming to conclusion

C# has many language constructs, which may make our code look simple, hiding a large implementation behind syntax sugar. Some of those constructs are questionable and hardly supportable, other ones are flexible and easily reproducible. When it comes to design, it is better to compose abstractions by objects, not by C#-dedicated language constructs. When it comes to performance, we should keep abstraction from memory management, freeing ourselves from emulating that by double costs.

Related News

Related Articles