Prove your code!

Sébastien Besnier

@_sebbes_

function toLink(url){
  let a = document.createElement('a');
  a.href = url;
  a.innerText = "Visit " + url;
  body.appendChild(a);
}



function toLink(url){
  let a = document.createElement('a');
  a.href = url;
  a.innerText = "Visit " + url;
  body.appendChild(a);
}



toLink("http://hello.com");




<a href=http://hello.com>
  Visit http://hello.com
</a>
function toLink(url){
  let a = document.createElement('a');
  a.href = url;
  a.innerText = "Visit " + url;
  body.appendChild(a);
}



toLink("http://hello.com");




toLink({url: "http://hello.com"});
<a href=http://hello.com>
  Visit http://hello.com
</a>
<a href="[object Object]">
  Visit [object Object]
</a>
function toLink(url){
  let a = document.createElement('a');
  a.href = url;
  a.innerText = "Visit " + url;
  body.appendChild(a);
}



toLink("http://hello.com");




toLink({url: "http://hello.com"});
<a href=http://hello.com>
  Visit http://hello.com
</a>
<a href="[object Object]">
  Visit [object Object]
</a>
Runtime Issue
a_file.js ⇾ a_file.ts

And you're done!

 

(okay, there are some tricks to know, but I only have 15 minutes!)

function toLink(url: string){
  let a = document.createElement('a');
  a.href = url;
  a.innerText = "Visit " + url;
  body.appendChild(a);
}

We have the proof that "url" is a string

function toLink(url: string){
  let a = document.createElement('a');
  a.href = url;
  a.innerText = "Visit " + url;
  body.appendChild(a);
}



toLink("http://hello.com");


toLink({url: "http://hello.com"});

We have the proof that "url" is a string

toLink("Hello there!");
toLink("Hello there!");

So types are useless?!

function toLink(url: string)
function toLink(url: string)

We want to prove we actually have an URL

http://hello.com/forum/search
http://hello.com/forum/search
PROTOCOL
DOMAIN
PATH
http://hello.com/forum/search
PROTOCOL
DOMAIN
PATH
type URL = {
  protocol: "http" | "https",
  domain: string,
  path: Array<string>,
}
http://hello.com/forum/search
PROTOCOL
DOMAIN
PATH
type URL = {
  protocol: "http" | "https",
  domain: string,
  path: Array<string>,
}
let url = {
  protocol: "http",
  domain: "hello.com",
  path: ["forum", "search"],
}
type URL = { /* ... */ };


function fromString(s: string): URL
{ /* ... */ }
URL.ts
type URL = { /* ... */ };


function fromString(s: string): URL | null
{ /* ... */ }


If s is not a valid url

URL.ts
class URL {
  private url: string;
  private constructor(url: string){
    this.url = url;
  }

 
  
  
  
  
  
  
  
}
class URL {
  private url: string;
  private constructor(url: string){
    this.url = url;
  }

  static fromString(s: string): URL      {
    if (isValidUrl(s)) return new URL(s);
    
  }


  
  
}
class URL {
  private url: string;
  private constructor(url: string){
    this.url = url;
  }

  static fromString(s: string): URL      {
    if (isValidUrl(s)) return new URL(s);
    
  }


  
  
}
class URL {
  private url: string;
  private constructor(url: string){
    this.url = url;
  }

  static fromString(s: string): URL      {
    if (isValidUrl(s)) return new URL(s);
    
  }


  
  
}

Proving isValidUrl

 

  • Unit tests

    • isValidUrl("blah") == false;
    • isValidUrl("http://hello.com") == true;
    • isValidUrl("http://he l o .com") == false;

class URL {
  private url: string;
  private constructor(url: string){
    this.url = url;
  }

  static fromString(s: string): URL      {
    if (isValidUrl(s)) return new URL(s);
    
  }


  
  
}

Proving isValidUrl

 

class URL {
  private url: string;
  private constructor(url: string){
    this.url = url;
  }

  static fromString(s: string): URL      {
    if (isValidUrl(s)) return new URL(s);
    
  }


  
  
}
class URL {
  private url: string;
  private constructor(url: string){
    this.url = url;
  }

  static fromString(s: string): URL|null {
    if (isValidUrl(s)) return new URL(s);
    else return null;
  }

  
  
  
}

If s is not a valid url

class URL {
  private url: string;
  private constructor(url: string){
    this.url = url;
  }

  static fromString(s: string): URL|null {
    if (isValidUrl(s)) return new URL(s);
    else return null;
  }

  toString(): string {
    return this.url;
  }
}

If s is not a valid url

type URL = { /* ... */ };


function fromString(s: string): URL | null
{ /* ... */ }




function toString(u: URL ): string
{ /* ... */ }

If s is not a valid url

URL.ts
type URL = { /* ... */ };


function fromString(s: string): URL | null
{ /* ... */ }




function toString(u: URL ): string
{ /* ... */ }

If s is not a valid url

This function cannot "fail"

URL.ts
type URL = { /* ... */ };


function fromString(s: string): URL | null
{ /* ... */ }




function toString(u: URL ): string
{ /* ... */ }

If s is not a valid url

This function cannot "fail"

URL.ts

Make fromString the only way to create URL values:

function toLink(url: URL){
  const a = document.createElement('a');
  const urlStr = url.toString();
  a.href = urlStr;
  a.innerText = "Visit " + urlStr;
  body.appendChild(a);
}

We have now the proof that "url" is an actual URL

const userInput = prompt("Enter an URL");
const url = URL.fromString(userInput);


toLink(url);
const userInput = prompt("Enter an URL");
const url = URL.fromString(userInput);


toLink(url);

const userInput = prompt("Enter an URL");
const url = URL.fromString(userInput);


toLink(url);

Turn on the

--strictNullChecks

TS option

const userInput = prompt("Enter an URL");
const url = URL.fromString(userInput);


toLink(url);


if(url === null) {
  alert("Not an url!");
} else {
  toLink(url);
}

Turn on the

--strictNullChecks

TS option

Encode business logic with the types!

function toLink(url: URL)
function toLink(url: URL): void
function toLink(url: URL): HTMLElement



function toLink(url: URL): Promise<Person>
function toLink(url: URL): HTMLElement



function toLink(url: URL): Promise<Person>

Return value gives the proof of what it performs

Case Study


LE FIGORA

 

TypeScript: prouvez votre code!

 

 

 

 

 

 

 

 

PUB: Buvez
Caco Calo

 

 

 

 

DECOUVREZ NOS PRODUITS
 


CACO CALO

 

http://figora.com
http://caco-calo.com


LE FIGORA

 

TypeScript: prouvez votre code!

 

 

 

 

 

 

 

 

PUB: Buvez
Caco Calo

 

 

 

 

DECOUVREZ NOS PRODUITS
 


CACO CALO

 

http://figora.com
http://caco-calo.com

AD SERVER

http://ad.com/caco?token=figora


LE FIGORA

 

TypeScript: prouvez votre code!

 

 

 

 

 

 

 

 

PUB: Buvez
Caco Calo

 

 

 

 

DECOUVREZ NOS PRODUITS
 


CACO CALO

 

http://figora.com
http://caco-calo.com

AD SERVER

http://ad.com/caco?token=figora
http://ad.com/caco?token=figora

How is this URL built?

http://ad.com/caco?token=figora
type Campaign = {
  // ...
  trackingUrl: Url,
  // ...
}
type Publisher = {
  // ...
  token: Token,
  // ...
}

Loaded independently at start-up

type Campaign = {
  // ...
  trackingUrl: Url,
  // ...
}
type Publisher = {
  // ...
  token: Token,
  // ...
}
function displayAd() {
  toLink(
    store.trackingUrl
    	.addParams(
          "token",
          store.token.toString()
   ))
}
type Campaign = {
  // ...
  trackingUrl: Url,
  // ...
}
type Publisher = {
  // ...
  token: Token,
  // ...
}

How to ensure:

  • the token is always added?
  • not added twice?
  • without "just having to think about it"?
function displayAd() {
  toLink(
    store.trackingUrl
    	.addParams(
          "token",
          store.token.toString()
   ))
}
type Campaign = {
  // ...
  trackingUrl: Url,
  // ...
}
type Publisher = {
  // ...
  token: Token,
  // ...
}
function displayAd() {
  toLink(
    store.trackingUrl
    	.addParams(
          "token",
          store.token.toString()
   ))
}

How to ensure:

  • the token is always added?
  • not added twice?
  • without "just having to think about it"?
type Campaign = {
  // ...
  trackingUrl: 
    ConfigurableUrl,
  // ...
}
type Publisher = {
  // ...
  token: Token,
  // ...
}
type Campaign = {
  // ...
  trackingUrl: 
    ConfigurableUrl,
  // ...
}
type Publisher = {
  // ...
  token: Token,
  // ...
}

Compile error

function displayAd() {
  toLink(store.trackingUrl);
}
class ConfigurableUrl {
  private url: Url;
  contructor(url: Url) {
    this.url = url;
  }
  
  configure(token: Token): Url {
    return this.url.addParam(
     "token",
      token.toString()
    )
  }
}
class ConfigurableUrl {
  private url: Url;
  contructor(url: Url) {
    this.url = url;
  }
  
  configure(token: Token): Url {
    return this.url.addParam(
     "token",
      token.toString()
    )
  }
}

The only way to get an Url back

class ConfigurableUrl {
  private url: Url;
  contructor(url: Url) {
    this.url = url;
  }
  
  configure(token: Token): Url {
    return this.url.addParam(
     "token",
      token.toString()
    )
  }
}
type Campaign = {
  // ...
  trackingUrl: 
    ConfigurableUrl,
  // ...
}

On loading

class ConfigurableUrl {
  private url: Url;
  contructor(url: Url) {
    this.url = url;
  }
  
  configure(token: Token): Url {
    return this.url.addParam(
     "token",
      token.toString()
    )
  }
}
type Campaign = {
  // ...
  trackingUrl: 
    ConfigurableUrl,
  // ...
}

On loading

function displayAd() {
  toLink(store.trackingUrl
        .configure(store.token)
     );
}

On display

Benefits

  • No architectural change
  • Only a few lines of code
  • Impossible to forget adding the token

 

Benefits

  • No architectural change
  • Only a few lines of code
  • Impossible to forget adding the token

 

Very high

ratio

GUARANTEES

EFFORT

BONUS:
Free Documentation

type Campaign = {
  // ...
  theme:
    Array<string>,
  trackingUrl:
    string,
  counter: number,  
  // ...
}

Level 1

BONUS:
Free Documentation

type Campaign = {
  // ...
  theme:
    Array<string>,
  trackingUrl:
    string,
  counter: number,  
  // ...
}
type Campaign = {
  // ...
  theme:
    Array<Color>,
  trackingUrl:
    ConfigurableUrl,
  counter: Counter, 
  // ...
}

Level 1

Level 2

WARNING

TypeScript can bite you!

You can cheat on type signatures

But, hey! my 10 minutes are almost (already?) gone!

Other Typing solutions for the Frontend

Elm

ReScript

PureScript

 

Questions?

Sébastien Besnier

@_sebbes_

Prove your code

By sebbes

Prove your code

  • 720