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
-
Unit tests
- isValidUrl("blah") == false;
- isValidUrl("http://hello.com") == true;
- isValidUrl("http://he l o .com") == false;
-
Property based testing a.k.a Fuzzy testing (random tests)
- Fast Check lib: https://github.com/dubzzz/fast-check/
- Talk (fr): https://youtu.be/lOnb50OpIec
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