Refactoring

mf-dallas“Whenever I have to think to understand what the code is doing, I ask myself if I can refactor the code to make that understanding more immediately apparent.”
― Martin Fowler, Refactoring: Improving the Design of Existing Code

Pada awal pengembangan aplikasi baru, biasanya anda hanya berfokus untuk membuat minimum viable app secepat mungkin. Kemudian seiring berjalannya waktu, anda ingin menambahkan fungsionalitas ke aplikasi anda. Mungkin pada awalnya kodenya tidak bercela, tetapi ketika fungsionalitasnya ditambah secara terus menerus, dan baris kode yang anda buat meningkat, maka bisa saja kode anda menjadi berantakan. Telltale sign dari ini adalah ketika anda membuka kode anda, anda tidak dapat mengerti apa yang anda tulis.

Kode berantakan ini dapat di deteksi dengan cepat dengan melihat apakah kode itu memiliki Code Smell, dan ini didokumentasikan secara ekstensif di buku Refactoring: improving the design of existing code yang dibuat oleh Martin Fowler. Beberapa dari code smell adalah:

  • Duplicated code: Kode yang sama digunakan di beberapa komponen yang berbeda.
  • Long Method: Metode yang panjang
  • Large Classes: Kelas yang memiliki fungsionalitas terlalu banyak
  • Long Parameter List: Metode yang untuk dieksekusi butuh banyak sekali parameter untuk dimasukkan kedalamnya
  • Comments: Tidak semua komentar itu buruk, hanya komentar yang digunakan untuk menjelaskan kode yang buruk.

Setelah mengetahui adanya Code Smell, maka anda harus membetulkannya. Saya akan memberikan salah satu contoh dari apa yang saya lakukan untuk membetulkan kode saya.

Extract Class

Pernahkah anda membuat komponen yang terlalu banyak barisnya pada render methodnya? Mungkin anda dapat mengekstraksinya menjadi beberapa komponen kecil.

Perhatikan snippet kode dibawah ini.

UploadFormPage.jsx

import React from 'react';
import UploadForm from './UploadForm';

/**
 * The entire form page. Consists of the two forms and a send button.
 */
class UploadFormPage extends React.Component {
    constructor(props) {
        super(props);
        this.schedule = React.createRef();
        this.rooms = React.createRef();
        this.submit = this.submit.bind(this);
        this.state = {
            buttonText: "Berikutnya"
        }
    }

    async submit() {
        this.setState({buttonText: "Mengupload..."})
        const schedIsValid = this.schedule.current.validateFile();
        const roomsIsValid = this.rooms.current.validateFile();

        if (schedIsValid && roomsIsValid) {
            await this.schedule.current.sendFile();            
            await this.rooms.current.sendFile();
            this.props.setState();
        }
    }
    render() {
        return <>
        <Container>
            <Row>
                <Col className={this.props.result === true ? 'inactive' : ''}>
                <button className="btn-circle" disabled>&nbsp;&nbsp;1&nbsp;&nbsp;</button>
                <h3 className="text-brown">Input dan Konfigurasi</h3>
                </Col>
                <Col className={this.props.result === false ? 'inactive' : ''}>
                <button className="btn-circle" disabled>&nbsp;&nbsp;2&nbsp;&nbsp;</button>
                <h3 className="text-brown">Output</h3>
                </Col>
            </Row>
        </Container>
        <div className="container bg-white h-75">
            <UploadForm
                apiUrl={this.props.schedulePostUrl}
                htmlFor="jadwal"
                label="jadwal kuliah"
                ref={this.schedule}
            />
            <br/>
            <UploadForm
                apiUrl={this.props.roomsPostUrl}
                htmlFor="ruang"
                label="ruang ujian"
                ref={this.rooms}
            />

            <div className="text-right p-3">
                <button className="btn btn-lg btn-primary" onClick={this.submit}>
                    {this.state.buttonText}
                </button>
            </div>
        </div>
        </>
    }
}

export default UploadFormPage;

SettingComponent.jsx

import React from 'react';
import RentangTanggalUjianComponent from '../date_range_input/rentangTanggalUjianComponent';
import JumlahSesiPerHariComponent from './JumlahSesiPerHariComponent.jsx';
import PilihTanggalUjianComponent from './PilihTanggalUjianComponent.jsx';
import SendButtonComponent from './SendButtonComponent'
import JumlahTanggalComponent from './JumlahTanggalComponent';

class SettingComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            dateRange: [new Date(2020, 6, 2), new Date(2020, 6, 10)],
            isPilihTanggalUjianComponentVisible: false,
            dateCount: 0,
            sessionPerDay: 1
        };
        this.dateComponent = React.createRef();
        this.sessionPerDayComponent = React.createRef();
    }

    setDateRange(startDate, endDate) {
        this.setState({
            dateRange: [new Date(startDate.valueOf()), new Date(endDate.valueOf())]
        });
    }

    setSessionPerDay(spd) {
        this.setState({
            sessionPerDay: spd
        });
    }

    componentDidMount() {
        this.setDateRange(new Date(2020, 6, 2), new Date(2020, 6, 10))
    }

    setIsPilihTanggalUjianComponentVisible(isVisible) {
        this.setState({
            dateCount: 0,
            isPilihTanggalUjianComponentVisible: isVisible
        });
    }

    submit() {
        const dates = this.dateComponent.current.state.dates_enabled;
        this.props.setState(dates, this.state.sessionPerDay);
    }

    setCount(delta) {
        this.setState({
            dateCount: this.state.dateCount + delta
        })
    }

    render() {
        return <>
        <Container>
            <Row>
                <Col className={this.props.result === true ? 'inactive' : ''}>
                <button className="btn-circle" disabled>&nbsp;&nbsp;1&nbsp;&nbsp;</button>
                <h3 className="text-brown">Input dan Konfigurasi</h3>
                </Col>
                <Col className={this.props.result === false ? 'inactive' : ''}>
                <button className="btn-circle" disabled>&nbsp;&nbsp;2&nbsp;&nbsp;</button>
                <h3 className="text-brown">Output</h3>
                </Col>
            </Row>
        </Container>
        <div className="container bg-white min-h-75">
            <RentangTanggalUjianComponent
                setVisibility={this.setIsPilihTanggalUjianComponentVisible.bind(this)}
                setDateRange={this.setDateRange.bind(this)}
            />
            <br />
            <JumlahSesiPerHariComponent
                update={this.setSessionPerDay.bind(this)}
            />
            <br />
            <PilihTanggalUjianComponent
                dateRange={this.state.dateRange}
                visible={this.state.isPilihTanggalUjianComponentVisible}
                setGeneratedDates={this.setGeneratedDates}
                ref={this.dateComponent}
                setCount={this.setCount.bind(this)} />
            <JumlahTanggalComponent
                count={this.state.dateCount}
            />
            <SendButtonComponent
                visible={this.state.isPilihTanggalUjianComponentVisible}
                submit={this.submit.bind(this)} />
        </div>
        </>
    }
}

export default SettingComponent;

Jika anda lihat, anda dapat menemukan kode yang diulang lebih dari sekali, yaitu:

<Container>
            <Row>
                <Col className={this.props.result === true ? 'inactive' : ''}>
                <button className="btn-circle" disabled>&nbsp;&nbsp;1&nbsp;&nbsp;</button>
                <h3 className="text-brown">Input dan Konfigurasi</h3>
                </Col>
                <Col className={this.props.result === false ? 'inactive' : ''}>
                <button className="btn-circle" disabled>&nbsp;&nbsp;2&nbsp;&nbsp;</button>
                <h3 className="text-brown">Output</h3>
                </Col>
            </Row>
        </Container>
Seperti anda tahu, duplicate code merupakan Code Smell. Jadi ketika anda menemukan hal ini, tanyakan kepada diri anda, apakah ada suatu hal yang dapat digunakan? Kita dapat mengeluarkannya dan membuat komponen baru dari itu. Komponen yang dibuat dapat menjadi seperti ini:
import React from 'react';
import './PointerComponent.css';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';

class PointerComponent extends React.Component {
    render() {
        return <Container>
            <Row>
                <Col className={this.props.result === true ? 'inactive' : ''}>
                <button className="btn-circle" disabled>&nbsp;&nbsp;1&nbsp;&nbsp;</button>
                <h3 className="text-brown">Input dan Konfigurasi</h3>
                </Col>
                <Col className={this.props.result === false ? 'inactive' : ''}>
                <button className="btn-circle" disabled>&nbsp;&nbsp;2&nbsp;&nbsp;</button>
                <h3 className="text-brown">Output</h3>
                </Col>
            </Row>
        </Container>
    }
}

export default PointerComponent;

Dengan ini, refactoring selesai dan tidak ada lagi code smell pada kedua file tersebut.

Dengan frekuensi refactoring yang meningkat seiring meningkatnya fungsionalitas, mungkin anda berpikir bahwa refactoring adalah waste of time. Mungkin anda memiliki intensi untuk membuat software yang sempurna terlebih dahulu agar anda bisa men-shave off waktu yang dibutuhkan untuk melakukan refactoring. Tetapi anda tidak bisa lebih salah lagi.

Dari pengalaman saya, saya rasa tidak ada yang bisa menulis kode yang sempurna pada penulisan yang pertama kali. Entah karena tidak memiliki pengalaman dalam menggunakan suatu framework tertentu, entah karena time constraints yang sangat ketat sehingga fitur harus di-develop sesegera mungkin, entah karena alasan lain yang tidak saya sebutkan (saya hanya menyebutkan apa yang saya alami). Dengan berpikir untuk men-develop suatu aplikasi yang sempurna dengan kode yang sempurna di awal, anda pasti akan menghabiskan waktu untuk memikirkan cara menyempurnakan fitur tersebut dibandingkan men-deliver appnya dengan fitur yang sesuai dengan keinginan anda.

Oleh karena itu, meski refactoring adalah suatu hal yang inevitable, anda jangan menghindarinya. Refactoring bukanlah suatu hal yang perlu ditakuti, melainkan harus di embrace, karena itu merupakan bagian yang sangat integral pada proses software development.

Leave a comment