Fix how to use RxJS

While working at REDACTED, I reported the following issue.

There is a little bug in our Core module which causes no harm at run time but is nonetheless very annoying because it defeats the purpose of using TypeScript instead of JavaScript, which is type matching. Given that the code editors we use support completion suggestions based on type declarations and type inference, it’s a real pity to loose those benefits because of that mosquito.

All of our access to Back end endpoints passes through copy-paste-adapted TypeScript methods whose return type is always set to something like this:

someEndpoint(params: ...): Observable<Response | OperationFailure> {
    // ...
}

while it should be

someEndpoint(params: ...): Observable<Response> {
    // ...
}

In fact, the omitted body of someEndpoint properly converts an HTTP response into an object of type Observable, but a success response is transformed into a Response type object and that is returned on the next channel of the observable, while a failure response is transformed into an OperationFailure type object and that is returned on the error channel of the observable.

Given that the ReactiveX code we use is programmed like this:

rxjs/src/internal/Observable.ts

export class Observable<T> implements Subscribable<T> {

rxjs/src/internal/types.ts

export interface Subscribable<T> {
  subscribe(observerOrNext?: PartialObserver<T> | ((value: T) => void),
            error?: (error: any) => void,
            complete?: () => void): Unsubscribable;
}

the expression Observable<T> means Observable whose ‘next’ values’ type is T. Hence, the return type of someEndpoint should be Observable<Response> instead of Observable<Response | OperationFailure>.

This fix would allow not only to improve code completion when programming services for accessing endpoints, but it would also avoid having to either cast values to their intended type or use values of type any just to keep the TypeScript compiler happy. Given that using values of type any is easier than casting them to their intended type, we now have mostly unchecked responses.

Speed up copy and paste programming

While working at REDACTED, I reported the following issue.

It’s perfectly valid to program by copy-pasting code from one place to another. This looks like a bold statement, but it’s sometimes very difficult to come up with a cleaner alternative. Of course I’m not talking about breaking the DRY rule. For example,

if (x && y && !z) {
    // ...
}
// later
if (x && y && !z) {
    // ...
}
// later
if (x && y && !z) {
    // ...
}

is a break of the DRY rule, and each programmer should replace that with

const condition = () => x && y && !z; // a function guarantees re-evaluation
if (condition()) {
    // ...
}
// later
if (condition()) {
    // ...
}
// later
if (condition()) {
    // ...
}

What I’m talking about is using a block of many lines of code as a template for generating a similar functionality at another place in the application. For example, I could have already programmed some page using two files: some-page.html and some-page.ts. Later on, when I need to program another-page, which is very similar to some-page, I can copy-and-paste the latter and apply the little changes needed to get the former.

This practice is very popular all around the world and there is no shame in doing it. And nothing stops you from writing a real code generator. However, copy-paste-adapt is all you need if you keep adapt at a minimum. How do you do so? Dead easy: refrain from using specific identifiers in code or move them from the code domain to the data domain.

For example, better names for that pair of files would be some/page.html and some/page.ts. This would allow me to copy the files in the some directory to the new another directory to immediately get another/page.html and another/page.ts files which preserve the structure without requiring any adaptation (like renaming).

Simplify translations

While working at REDACTED, I reported the following issue.

We develop in Spain an application in English for Saudi Arabia, how is it that we don’t already have easy to use translations?

Our translations are cumbersome to manage. To add one you have to:

  1. create a hierarchical key like some-module.some-part.some-section.some-title
  2. add its translation in code like this.title = this.translateService.instant('some-module.some-part.some-section.some-title');
  3. open a terminal window at the app main directory, then issue $ npm run translation.export
  4. open a browser, then navigate to a third party website and authenticate
  5. search some-module.some-part.some-section.some-title
  6. select your key in the results (beware that approximate results are shown too)
  7. edit its translation in a dialog box
  8. save the transaltion
  9. open a terminal window at the app main directory, then issue: $ npm run translation.import
  10. reload the page to show the translated title

Why can’t I just add the translated key to a translations file? I guess because the programmers that started developing the app were required to use that third party website for allowing professional translators to do their job. That is a reasonable requirement but there is no need for programmers to continually export and import translations, simply to make them appear on the page they are working on. Translations could automatically be exported and imported at any later time, like when merging changes into the development branch.

It wouldn’t be difficult to write an extraction script to allow coding like this:

// .../src/some-module/some-part/some-section.translations.ts
export const translations = {
    "some-title": "..."
};

// .../src/some-module/some-part/some-section.ts
this.title = x('some-title');

There is a problem with extraction scripts, though. They are static text analyzers that read code and extract some.key from expressions like instant('some.key'). Thus they can’t extract interpolated keys like instant(`${some}.key`), where some is a string variable whose value will be set later. For example, if some = 'another', then ${some}.key would be another.key.

Interpolated keys are very useful for compressing many code lines into one cleaner expression. For example, this code

let translation;
switch (some) {
    case 'some':
        translation = x('some.key');
        break;
    case 'another':
        translation = x('another.key');
        break;
    case 'yet.another':
        translation = x('yet.another.key');
        break;
    default: // programmer error
        throw new Error(`Unexpected value for 'some' variable (got '${some}')`);
}

can be compressed to this one liner:

const translation = x(`${some}.key`);

While it’s true that we can’t have both the independence from run time (static analyzer) and the flexibility of interpolations (dynamic analyzer) in the same tool, nonetheless we can easily have both benefits with a static analyzer and a bit of overhead. In fact, all we need to do is to declare all those interpolated keys in a file that only needs to exist. (no need to use it anywhere)

// interpolated-translation-keys.ts
x('some.key');
x('another.key');
x('yet.another.key');

With a file like that, the static analyzer would find it, eat it, and spit out translatable keys, which could eventually be exported. At the same time, the code would work perfectly with interpolated translation keys.