GithubHelp home page GithubHelp logo

articles's People

Contributors

dependabot[bot] avatar jichang avatar

Watchers

 avatar  avatar

articles's Issues

Data validation

As a FE programmer, You, might like me, spend a lot of time on error handling every day. And most of these errors are caused by invalid data, like incorrect function paramters, illegal user input, incomplete api response, malware data attacks. You might write a lot of defensive code here and there to handle those errors. But is there a better way to solve this kind of problem ?

Basically, there are 2 kinds of data validation, each of them has their own solutions for finding and handling errors.

  1. internal data validation

Internal data are created and consumed in the same system, maybe it's defined for internal usage or created from validated input. For this kind of data, because you have full control of the data, you should be able to handle this problem safely.

There are 2 common ways for internal data validation: types and tests. On the internet, a lot of people argue about which is necessary for robust programing, tests or types ? In my opinion, both of them are necessary and there two methods can help you developing great and robust program.

Types: Make Illegal States Unrepresentable

"Make Illegal States Unrepresentable" is a famous phrase from Yaron Minsky in 2011. The idea of this phrase is that instread of handling data errors, you shouldn't be able to write illegal data at first place. When I started FE development several years ago, it always bothers me that people use string lateral as function arguments. Like

$.fullPage({
  // ...
  easing: "easeInOutCubic",
  easingcss3: "ease"
  // ...
});

Until I learned JavaScript and realized that it doesn't have enum and types, you can only use magic numbers and strings to describe options. The consequence of this is that library users need to spend mins even hours on debugging until they find out it's a typo error. On the other hand, responsible library writers need to write a lot of checking code, like below

function fullPage() {
  // ...
  if (easing !== "easeInOutCubic") {
    // Normally, JS library will not throw exception, because it might
    // crash user's applciation if they don't catch exceptions, so
    // they intend to log some warning message and use some defaut config to continue
    // In my option, it's worse for debugging and investigating
    throw Error("invalid ease option");
  }
  // ...
}

It's the price we paid for dynaimc typed language. That's why JS programmers feel good when writing small libaries, but when working on big projects, they will lose their confidence.

Fortunately, we have TypeScript/Flow nowadays. In there languages, they support string lateral types and union types. It can significantly improve the safety and maintainibility of code base.

type Direction = "up" | "down" | "left" | "right";

function turn(direction: Direction) {
  switch (direction) {
    case "up":
      break;
    // ...
  }
}

And if you are working on some new projects and you don't mind learning new languages, I sugguest you try some transpiled-to-javascrpt stack, like Fable, Elm or Reason.

// this is a demo written with Elmish(https://elmish.github.io/elmish/index.html)
type Model =
    { x : int }

type Msg =
    | Increment
    | Decrement

open Elmish

let init () =
    { x = 0 }, Cmd.ofMsg Increment

let update msg model =
    match msg with
    | Increment when model.x < 3 ->
        { model with x = model.x + 1 }, Cmd.ofMsg Increment

    | Increment ->
        { model with x = model.x + 1 }, Cmd.ofMsg Decrement

    | Decrement when model.x > 0 ->
        { model with x = model.x - 1 }, Cmd.ofMsg Decrement

    | Decrement ->
        { model with x = model.x - 1 }, Cmd.ofMsg Increment

Program.mkProgram init update (fun model _ -> printf "%A\n" model)
|> Program.run

I highly recommended SAFE stack if you are interested. It's a unversal web framework. You can write your application in the same language F#. All your frontend and backend will be written in F#, it's easy to share your domain model between BE and FE. F# is good, it has great but not too complicated type system. The whole stack runs on .Net Core, it's cross platform and you can use a lot of great tech from Microsoft and Community.

Tests: Make Illegal Logic Unexecutable

People using strong typed language always say "Once it compiles, it works". Unfortunately, this is not true. Even though, types can eliminate some kinds fo bugs, it can not eliminate all bugs. Great type system can make sure several bad thing will never happer, like you don't get a number if you want a string, you can not use a unknown property or the null referece problem. But business logic or algorithm error can not be solved by type system, thought it helps a lot.

In this case, you need tests. To be honest, I don't write a lot of tests, I'm lazy and most of my daily work has QA team, and again I'm really lazy. But if you ask me, I do have some suggestions for this

a. run tests automatically
b. use types to make sure that all cases are handled, like options property...
c. write unit tests for critical module/function, no need to pursue 100% code coverage. I suggest you learn about property based testing, in my opioion, reproducable random tests is more reliable than manually defined test.
d. write integration tests for important business process.

  1. external data validation

Once you make internal system safe, you can deal with the cruel world now.

For this part, if you are using static typed languages, most of time you will be fine. Because the type system will require you validate data before you get data from outside world. Basically, you need to remember 2 things

a. using proper validation when doing serialization/deserialization

Please remeber, allocate a memory chunk for data then use some magic type conversion to get what you want is totally wrong way for deserialization, vice verse.

b. don't use raw pointer conversion (This for C/C++ programmers).

If you are using dynamic typed languages, the suggestion is simple: just make you code runs like a static typed language. For example, you might used to write code like this

interface User {
  id: number;
  name: string;
  age?: number;
  email: string;
}

let user: User = await callApiOrUseUserInput();
// ....

The assumption here is that call api or user input will always return valid data which match the requirement of interface User. But this is not always true, like backend has incompatible change, user input invalid data. So to make sure there bad things will not effect the behavior of program, you need to validate data before using.

interface User {
  id: number;
  name: string;
  age?: number;
  email: string;
}

let data: any = await callApiOrUseUserInput();
// In function validate, youu need to check data match the interface User,
// like id, name, email has the right type and value.
// For age, you need to make sure it either doesn't exist or it's a number.
let result: User | Error = validate(data);
// You need to handle error implicitly

For error handling of invalid data from outside world, I will suggest the philosophy of Erlang: Let it crash. Because PM/Operators/Users might not think it's a error, they might raise a ticket or they will continue their process, which will make the debugging and reproduce steps much more difficult. Of cource, it you are working on FE, we can't just let app crash, it will heart user experience. In this case, we can setup some global handler to show popup or error message, but never ever let the program run with default config silently. It will be a nightmare.

Combine Lens & Context & Hooks to manage global state in React

Complete Code: issue5

Recently I was working on a personal projects with React. Since it's not a big projects, I decide to get rid of any state management library.

For component level, it's simple, we can just use Hooks to solve this, like useState, useReducer. But how to manage a global state in a React app ? Luckily, we have Context. so we can just manage state in top level component with useReducer, and leverage Context to pass those data to child component.

export interface IProfile {
  name: string;
  age: number;
  country: string;
}

export interface IUser {
  id: number;
  profile: IProfile
}

export interface IStore {
  user: IUser;
}

export const initialStore: IStore = {
  user: {
    id: 0,
    profile: {
      name: 'initial user name',
      age: 0,
      country: 'anywhere'
    }
  },
}

export type Action = (store: IStore) => IStore; // explain later

export interface IStoreContext {
  store: IStore,
  dispatch: (action: Action) => void;
}

export const initialContext: IStoreContext = {
  store: initialStore,
  dispatch: () => {
    throw new Error('this initial function should never be called')
  }
}

export const StoreContext = React.createContext<IStoreContext>(initialContext);

// explain later
export function reducer(store: IStore, setter: Action) {
  return setter(store);
}
import React from 'React';

import { reducer, initialStore, StoreContext } from './Context/StoreContext';

export function App() {
    <StoreContext.Provider value={{ store, dispatch }}>
      <div className="App">
        <header className="App-header">
          <StoreContext.Consumer>
            {({ store, dispatch }) => {
              return (
                <div>
                  <p>{JSON.stringify(store)}</p>
                </div>
              )
            }}
          </StoreContext.Consumer>
        </header>
      </div>
    </StoreContext.Provider>
}

It looks good so far, we have solve the global state management with Context & Hooks. but there's several problem bother me a lot. Normally, when we use reducer, we intend to define a lot of action and update store use a big switch statement.

export interface IUpdateUserName {
  kind: 'updateUserName'
  payload: {
    username: string
  }
}

type Action = UpdateUserName

export function reducer(store: IStore, action: Action) {
  switch(action.kind) {
    case 'updateUserName':
        return {
            ...store,
            user: {
                ...store.user,
               profile: {
                   ...store.user.profile,
                   username: action.payload.username
               }
            }
        };
    break;
  }
}


// Then we can dispatch action in component like this
dispatch({
  action: 'updateUserName',
  payload: {
    username: 'new user name'
  }
})

Consider the code above, it's really not a joy to update nested property in state, even though spread operator has save us a lot of work and type checking can make sure we don't update the wrong field, but can we make it better ?

Then I really why not use Lens and just dispatch a setter ? This is why at first, the action type is defined as

export type Action = (store: IStore) => IStore

If you don't familiar with Lens, you can consider it as a combination of getter and setter function. Getter is used to read value and Setter is used to update value. Here's a simple version of Lens

export interface ILens<A, B> {
    get: (a: A) => B;
    set: (b: B) => (a: A) => A;
}

// construct a Lens from property name
// get will be a function to read property object object
// set will be a function to set value of object
export function lens<A, P extends keyof A>(prop: P): ILens<A, A[P]> {
    return {
        get: (a: A) => {
            return a[prop];
        },
        set: (propValue: A[P]) => {
            return (a: A) => {
                return {
                    ...a,
                    [prop]: propValue,
                }
            }
        }
    }
}

// compose can combine a fuction to form another Lens
//  it's useful when we want to read/write nested value
export const compose = <A, B, C>(lensAB: ILens<A, B>) => {
    return (lensBC: ILens<B, C>): ILens<A, C> => {
        return {
            get: (a: A) => {
                return lensBC.get(lensAB.get(a))
            },
            set: (c: C) => {
                return (a: A) => {
                    const b = lensAB.get(a);
                    const updatedB = lensBC.set(c)(b)
                    return lensAB.set(updatedB)(a)
                }
            }
        }
    }
}

Next, we can define some lens for IStore property and see how to dispatch lens to update username

export const storeUserLens = lens<IStore, 'user'>('user');
export const userProfileLens = lens<IUser, 'profile'>('profile')
export const profileNameLens = lens<IProfile, 'name'>('name');

export const storeUserProfileLens =
  compose<IStore, IUser, IProfile>(storeUserLens)(userProfileLens)

export const storeUserProfileNameLens =
  compose<IStore, IProfile, string>(storeUserProfileLens)(profileNameLens)



// In component, we can use getter to retrive nested value and dispatch a setter to update user name
          <StoreContext.Consumer>
            {({ store, dispatch }) => {
              return (
                <div>
                  <p>{storeUserProfileNameLens.get(store)}</p>
                  <button type="button" onClick={evt => {
                    dispatch(storeUserProfileNameLens.set('new user name'));
                  }}>Update name</button>
                </div>
              )
            }}
          </StoreContext.Consumer>

Note, this lens definition is not very well formed, it you want to use Lens in your project, you can try monocle-ts

Syntax Consistency of Assignment

Assignment is a fundamental operation, consistency is an essential requirement for programming language syntax, but surprisingly, there are some languages which don't provide consistent syntax for assignemnt.

For example, in JavaScript, most of time, when you want to do assignment, you need to use "=" operator.

let str = "a"
let num = 0

let fun = function () {
  console.log("hello, world")
};

but there's one exception. When you want to create an object, somehow you need to use ":" to assign value to property.

let a = {
  field: 1  // why ":" instead of "="
};

But in F#/Haskell and other ML family languges, "=" is used for property assignment in records

let a = { filed = 1 }

Method chaining/Fluent Interfaces

Method chaining/Fluent Interfaces is a very common programming pattern in OO programming languages. It's often used when you want to calling multiple functions or same function mutiple times on the same object.

In frontend, the most famous library using this pattern should be jQuery.

$('some-dom-id')
  .css('background-color', 'blue')
  .data('purpose', 'test')
  .hide().

In backend, people usually use method chaining in these so-called builder pattern, like

PeopleBuilder pb = new PeopleBuilder();
People p = pb.setName("John Doe")
    .setAge(10)
    .setSex("male")
    .build();

Even though it all looks similar, there might have some subtle differences between different languages when implementing this pattern.

Normally in OO languages, you need to return this/self in those chained functions

class People {
  constructor() {
    this.name = "";
    this.age = 0;
    this.sex = "male";
  }

  setName(name) {
    this.name = name;
    return this;
  }

  setAge(age) {
    this.age = age;
    return this;
  }

  setSex(sex) {
    this.sex = sex;
    return this;
  }

  replaceName(newName) {
    let oldName = this.name;
    this.name = newName;

    return oldName;
  }
}

let people = new People();
people
  .setName("John Doe")
  .setAge(10)
  .setSex("male");

But this approach has one problem: you might not able to call functions that has return values, like function replaceName in the example above, as you can't return two values at the same time.

How to solve this problem ?

  1. If you are using languagues like C# that has out or ref parameters, you can overcome this by using an extra parameter.

    class People {
      //....
      People swapName(String newName, out String oldName) {
        oldName = this.name;
        this.name = newName;
        return this;
      }
    }

    People always say XX doesn't need fancy features and it will make code less readable, but my opinion is that it's pretty good to have those features when you really need it and it's not those fancy feature makes code less readable, it's the way how you use it.

  2. Some languages designers think it's an very essential programming pattern and it even deserves an operator, like Pony, it has a specifial operatro .> for this purpose.

    class People
      var name: String
      var age: U32
    
      new create(name': String, age': U32) =>
        name = name'
        age = age'
    
      fun ref setName(name': String) =>
        name = name'
    
      fun ref setAge(age': U32) =>
        age = age'
    
    actor Main
      new create(env: Env) =>
        let people = People("init", 10)
        people
          .>setName("nameA")
          .>setAge(10)
        env.out.print("Hello, world!")

    Basically, in Pony,

    obj.>method()

    is equal to

    (obj.method();obj)
    

    Pony is expression-based, so the value of above code is the value of last expression obj

    You can see that sometimes one build-in operator will save a log of work and make the code much more flexible, you don't even need to change the existing code.

    In those flexiable dynamically languages like JavaScript, you can build your own .> operator by functions. The naive way can be

    class People {
      // ....
    
      chain(fun, args) {
        fun.apply(this, args);
    
        return this;
      }
    }
    
    let people = new People();
    people
      .chain(people.setName, ["name"])
      .chain(people.setAge, [10]);
    

Customizable Sorting

Recently, I met a small but interesting problem, it deserves a short article.

The problem is that we are building a vertical page and there are several sections in this page, like

*************
| section A |
*************
| section B |
*************
| section C |
*************
| section D |
*************
 ...

We use FlatList to render this page, in case you never write React Native before or don't know what's FlatList, I will a naive loop to represent the rendering process

let sections = [
  "section A",
  "section B",
  "section C",
  "section D"
  // ...
];

render() {
  let components = sections.map(section => {
    let data = getData(section);

    // renderComponent will create component based on section and data
    return renderComponent(section, data);
  })

  return <View>{components}</View>
}

Then we got a requirement, sections need different order based on user's current country. The best solution might be setup a CMS-like system and let PM or operations to manually change the orders. In our code, we can just call one api and get the order of sections. the code is pretty much the same, except sections is not hardcoded any more.

But if we just want to solve this by modifiing current code, basically we have too ways

  1. use different orders for different country. By this way, we need to copy the order of all sections for every country and change the order by need. For further changes, we only need to change the data in our code and we don't need to touch the logic. Even if we don't want to show some sections, we just need to remove the corresponding line.
let orders = {
  countryA: [
    "section A",
    "section B",
    "section C",
    "section D"
    // ...
  ],
  countryB: [
    "section D",
    "section C",
    "section A",
    "section B"
    // ...
  ]
  // ...
};
let currentCountry = 'countryA';

render() {
  let sections = orders[currentCountry];

  let components = sections.map(section => {
    let data = getData(section);

    // renderComponent will create component based on section and data
    return renderComponent(section, data);
  })

  return <View>{components}</View>
}

Pros: simple, elegant, easy to debug and change.

Cons: when more countries and sections are added, this code will become longer, tedious and hard to maintain, especially when we have a lot of sections and only a few of them need different order based on country.

  1. create default sorting order with interval and change the order by country.
let sections = [
  "section A",
  "section B",
  "section C",
  "section D"
  // ...
];

let sectionsCount = sections.length;

// Here we don't use 0, 1, 2, 3 ... as orders, if you use continuous integers, once
// you want to move the last one to the second one, you might need to change a lot of code
let defaultOrders = {
  "section A": sectionsCount * 1,
  "section B": sectionsCount * 2,
  "section C": sectionsCount * 3,
  "section D": sectionsCount * 4,
  // ...
}

let orders = {
  ...defautOrders
};

if (currentCountry === "countryB") {
  orders["section D"] = defaultOrders["sectionsA"] - 2;
  orders["section C"] = defaultOrders["sectionsA"] - 1;
}

render() {
  let components = sections.sort((a, b) => {
    return orders[a] - orders[b];
  }).map(section => {
    let data = getData(section);

    // renderComponent will create component based on section and data
    return renderComponent(section, data);
  })

  return <View>{components}</View>
}

Pros: less duplicated code and esay to scale

Cons: more triky and more complex code.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.