Refactoring yang baik atau refactoring yang buruk

Refactoring yang baik atau refactoring yang buruk
[ad_1]
Saya telah mempekerjakan banyak pengembang selama bertahun-tahun. Beberapa dari mereka yakin bahwa kode kami memerlukan pemfaktoran ulang yang ekstensif. Namun inilah masalahnya: di hampir semua kasus, kode mereka yang baru difaktorkan ulang dianggap oleh pengembang lain lebih sulit untuk dipahami dan dipelihara. Umumnya juga lebih lambat dan lebih bermasalah.
Jangan salah paham. Refactoring itu sendiri tidak buruk. Ini adalah bagian penting dalam menjaga basis kode yang sehat. Masalahnya adalah refactoring yang buruk itu buruk. Dan ternyata sangat mudah untuk terjebak dalam membuat keadaan menjadi lebih buruk ketika mencoba memperbaikinya.
Jadi mari kita lihat apa yang membedakan refactor yang baik dari yang buruk, dan bagaimana menghindari menjadi pengembang yang ditakuti semua orang di dekat basis kode.
Yang baik, yang buruk dan jelek dari refactoring
Abstraksi bisa menjadi hal yang baik. Abstraksi bisa berdampak buruk. Kuncinya adalah mengetahui kapan dan bagaimana menerapkannya. Mari kita lihat beberapa kendala umum dan cara menghindarinya.
1. Mengubah gaya pengkodean secara signifikan
Salah satu kesalahan paling umum yang pernah saya lihat adalah ketika pengembang sepenuhnya mengubah gaya pengkodean selama refactor. Hal ini sering terjadi ketika seseorang berasal dari latar belakang berbeda atau memiliki pendapat kuat tentang paradigma pemrograman tertentu.
Mari kita lihat sebuah contoh. Bayangkan kita memiliki sepotong kode yang perlu dibersihkan:
// this code could be cleaner
function processUsers(users: User[]) {
const result = [];
for (let i = 0; i < users.length; i++) {
if (users[i].age >= 18) {
const formattedUser = {
name: users[i].name.toUpperCase(),
age: users[i].age,
isAdult: true
};
result.push(formattedUser);
}
}
return result;
}
Pemfaktor ulang yang buruk
import * as R from 'ramda';
// adopted a completely different style + library
const processUsers = R.pipe(
R.filter(R.propSatisfies(R.gte(R.__, 18), 'age')),
R.map(R.applySpec({
name: R.pipe(R.prop('name'), R.toUpper),
age: R.prop('age'),
isAdult: R.always(true)
}))
);
Meskipun versi refactored ini mungkin menarik bagi penggemar pemrograman fungsional, versi ini memperkenalkan perpustakaan baru (Ramda) dan gaya pengkodean yang benar-benar berbeda. Bagi tim yang tidak terbiasa dengan pendekatan ini, hal ini bisa menjadi mimpi buruk untuk dipertahankan.
Refaktor yang bagus
// cleaner and more conventional
function processUsers(users: User[]): FormattedUser[] {
return users
.filter(user => user.age >= 18)
.map(user => ({
name: user.name.toUpperCase(),
age: user.age,
isAdult: true
}));
}
Versi ini menyempurnakan kode asli dengan menggunakan metode JavaScript yang lebih idiomatis seperti filter
Dan map
. Ini lebih ringkas dan mudah dibaca, tetapi tidak memperkenalkan paradigma baru atau ketergantungan eksternal.
2. Abstraksi yang tidak perlu
Saya pernah mempekerjakan seseorang yang menambahkan banyak abstraksi baru tanpa memahami kode yang mendasarinya. Mereka mulai mengelompokkan hal-hal yang tidak boleh dikelompokkan bersama dan (dengan sengaja) menyimpang seiring berjalannya waktu. Mereka menggabungkan beberapa konfigurasi yang seharusnya tidak dikonsolidasikan (API yang berbeda memerlukan konfigurasi yang berbeda).
Sebelum
// this code could be cleaner
function processUsers(users: User[]) {
const result = [];
for (let i = 0; i < users.length; i++) {
if (users[i].age >= 18) {
const formattedUser = {
name: users[i].name.toUpperCase(),
age: users[i].age,
isAdult: true
};
result.push(formattedUser);
}
}
return result;
}
Pemfaktor ulang yang buruk
// there are way more layers and abstractions here than necessary
class UserProcessor {
private users: User[];
constructor(users: User[]) {
this.users = users;
}
public process(): FormattedUser[] {
return this.filterAdults().formatUsers();
}
private filterAdults(): UserProcessor {
this.users = this.users.filter(user => user.age >= 18);
return this;
}
private formatUsers(): FormattedUser[] {
return this.users.map(user => ({
name: this.formatName(user.name),
age: user.age,
isAdult: true
}));
}
private formatName(name: string): string {
return name.toUpperCase();
}
}
const processUsers = (users: User[]): FormattedUser[] => {
return new UserProcessor(users).process();
};
Refactor ini memperkenalkan kelas dengan beberapa metode, yang mungkin tampak lebih “berorientasi objek”, namun sebenarnya lebih kompleks dan sulit untuk dipahami secara sekilas.
Refaktor yang bagus
// cleaner and more conventional
const isAdult = (user: User): boolean => user.age >= 18;
const formatUser = (user: User): FormattedUser => ({
name: user.name.toUpperCase(),
age: user.age,
isAdult: true
});
function processUsers(users: User[]): FormattedUser[] {
return users.filter(isAdult).map(formatUser);
}
Versi ini memecah logika menjadi fungsi-fungsi kecil yang dapat digunakan kembali tanpa menimbulkan kerumitan yang tidak perlu.
3. Menambah inkonsistensi
Saya telah melihat kasus di mana pengembang memperbarui bagian dari basis kode agar berfungsi sepenuhnya berbeda dari yang lain, dalam upaya menjadikannya “lebih baik”. Hal ini sering kali menimbulkan kebingungan dan frustrasi bagi pengembang lain yang harus beralih konteks di antara gaya yang berbeda.
Katakanlah kita mempunyai aplikasi React dimana kita secara konsisten menggunakan React Query untuk pengambilan data:
// Throughout the app
import { useQuery } from 'react-query';
function UserProfile({ userId }) {
const { data: user, isLoading } = useQuery(['user', userId], fetchUser);
if (isLoading) return Loading...
;
return {user.name}
;
}
Sekarang bayangkan seorang pengembang memutuskan untuk menggunakan Redux Toolkit untuk satu komponen:
// One-off component
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPosts } from './postsSlice';
function PostList() {
const dispatch = useDispatch();
const { posts, status } = useSelector((state) => state.posts);
useEffect(() => {
dispatch(fetchPosts());
}, [dispatch]);
if (status === 'loading') return Loading...
;
return {posts.map(post => {post.title}
)};
}
Ketidakkonsistenan ini membuat frustrasi karena memperkenalkan model pengelolaan negara yang sangat berbeda untuk satu komponen.
Pendekatan yang lebih baik adalah tetap menggunakan React Query:
// Consistent approach
import { useQuery } from 'react-query';
function PostList() {
const { data: posts, isLoading } = useQuery('posts', fetchPosts);
if (isLoading) return Loading...
;
return {posts.map(post => {post.title}
)};
}
Versi ini menjaga konsistensi, menggunakan React Query untuk pengambilan data di seluruh aplikasi. Ini lebih sederhana dan tidak mengharuskan pengembang lain mempelajari model baru untuk satu komponen.
Ingatlah bahwa konsistensi dalam basis kode Anda itu penting. Jika Anda perlu memperkenalkan model baru, pertama-tama pikirkan bagaimana Anda bisa mendapatkan dukungan dari tim Anda, daripada menciptakan ketidakkonsistenan yang hanya terjadi satu kali saja.
4. Tidak memahami kode sebelum melakukan refactoring
Salah satu masalah terbesar yang saya temui adalah pemfaktoran ulang kode ketika pelajarilah secara berurutan memiliki mempelajarinya. Ini adalah ide yang sangat buruk. Saya telah melihat komentar bahwa Anda harus bekerja dengan kode tertentu selama 6-9 bulan. Jika tidak, Anda berisiko menimbulkan bug, mengganggu kinerja, dll.
Sebelum
// a bit too much hard coded stuff here
function fetchUserData(userId: string) {
const cachedData = localStorage.getItem(`user_${userId}`);
if (cachedData) {
return JSON.parse(cachedData);
}
return api.fetchUser(userId).then(userData => {
localStorage.setItem(`user_${userId}`, JSON.stringify(userData));
return userData;
});
}
Pemfaktor ulang yang buruk
// where did the caching go?
function fetchUserData(userId: string) {
return api.fetchUser(userId);
}
Pemfaktoran ulang mungkin mengira mereka menyederhanakan kode, namun sebenarnya mereka menghapus mekanisme cache penting yang ada untuk mengurangi panggilan API dan meningkatkan kinerja.
Refaktor yang bagus
// cleaner code preserving the existing behavior
async function fetchUserData(userId: string) {
const cachedData = await cacheManager.get(`user_${userId}`);
if (cachedData) {
return cachedData;
}
const userData = await api.fetchUser(userId);
await cacheManager.set(`user_${userId}`, userData, { expiresIn: '1h' });
return userData;
}
Pemfaktoran ulang ini mempertahankan perilaku caching sekaligus berpotensi memperbaikinya dengan menggunakan pengelola cache yang lebih canggih dengan masa berlaku.
5. Memahami konteks bisnis
Saya pernah bergabung dengan perusahaan dengan latar belakang kode lama yang buruk. Saya memimpin proyek untuk memigrasikan bisnis e-commerce ke teknologi baru, modern, lebih cepat, dan lebih baik… Angular.js.
Ternyata bisnis ini sangat bergantung pada SEO dan kami membuat aplikasi satu halaman yang lambat dan besar.
Kami tidak mengirimkan apa pun selama 2 tahun kecuali salinan karbon situs web yang lebih lambat, lebih bermasalah, dan kurang dapat dipelihara. Untuk apa? Orang-orang yang menjalankan proyek ini (saya: Saya bajingan dalam skenario ini) belum pernah bekerja di situs ini sebelumnya. Saya masih muda dan bodoh.
Mari kita lihat contoh modern dari kesalahan ini:
Pemfaktor ulang yang buruk
// a single page app for an SEO-focused site is a bad idea
function App() {
return (
);
}
Pendekatan ini mungkin tampak modern dan bersih, namun sepenuhnya dilakukan di sisi klien. Untuk situs e-commerce yang sangat bergantung pada SEO, hal ini bisa menjadi bencana.
Refaktor yang bagus
// server render an SEO-focused site
export const getStaticProps: GetStaticProps = async () => {
const products = await getProducts();
return { props: { products } };
};
export default function ProductList({ products }) {
return (
...
);
}
Pendekatan berbasis Next.js ini menyediakan rendering sisi server yang siap digunakan, yang sangat penting untuk SEO. Ini juga memberikan pengalaman pengguna yang lebih baik dengan pemuatan halaman awal yang lebih cepat dan peningkatan kinerja bagi pengguna dengan koneksi yang lebih lambat. Remix akan sama efektifnya untuk tujuan ini, memberikan manfaat serupa untuk rendering sisi server dan optimasi SEO.
6. Kode terlalu terkonsolidasi
Saya pernah mempekerjakan seseorang yang, pada hari pertamanya bekerja di backend kami, segera mulai memfaktorkan ulang kodenya. Kami memiliki banyak fungsi Firebase, beberapa dengan pengaturan berbeda dari yang lain, seperti batas waktu dan alokasi memori.
Seperti inilah tampilan awal pengaturan kami.
Sebelum
// we had this same code 40+ times in the codebase, we could perhaps consolidate
export const quickFunction = functions
.runWith({ timeoutSeconds: 60, memory: '256MB' })
.https.onRequest(...);
export const longRunningFunction = functions
.runWith({ timeoutSeconds: 540, memory: '1GB' })
.https.onRequest(...);
Orang ini memutuskan untuk menggabungkan semua fungsi ini menjadi satu createApi
fungsi.
Refaktor yang buruk
// blindly consolidating settings that should not be
const createApi = (handler: RequestHandler) => {
return functions
.runWith({ timeoutSeconds: 300, memory: '512MB' })
.https.onRequest((req, res) => handler(req, res));
};
export const quickFunction = createApi(handleQuickRequest);
export const longRunningFunction = createApi(handleLongRunningRequest);
Pemfaktoran ulang ini menyetel semua API agar memiliki parameter yang sama, tanpa ada cara untuk menggantinya per API. Ini menjadi masalah karena terkadang kita memerlukan pengaturan berbeda untuk fungsi berbeda.
Pendekatan yang lebih baik adalah dengan mengizinkan opsi Firebase melalui API.
Refaktor yang bagus
// setting good defaults, but letting anyone override
const createApi = (handler: RequestHandler, options: ApiOptions = {}) => {
return functions
.runWith({ timeoutSeconds: 300, memory: '512MB', ...options })
.https.onRequest((req, res) => handler(req, res));
};
export const quickFunction = createApi(handleQuickRequest, { timeoutSeconds: 60, memory: '256MB' });
export const longRunningFunction = createApi(handleLongRunningRequest, { timeoutSeconds: 540, memory: '1GB' });
Dengan cara ini kami mempertahankan manfaat abstraksi sekaligus menjaga fleksibilitas yang kami perlukan. Saat mengkonsolidasikan atau mengabstraksi, selalu pikirkan kasus penggunaan yang Anda usulkan. Jangan korbankan fleksibilitas untuk kode yang “lebih bersih”. Pastikan abstraksi Anda mengizinkan fungsionalitas penuh yang disediakan oleh implementasi asli.
Dan serius, pahami kodenya sebelum Anda mulai “memperbaikinya”. Saat berikutnya kami menerapkan beberapa API, kami mengalami masalah yang sebenarnya bisa dihindari tanpa pemfaktoran ulang buta ini.
Cara melakukan refactor dengan benar
Perlu dicatat bahwa Anda perlu memfaktorkan ulang kodenya. Tapi lakukan dengan baik. Kode kita belum sempurna, kode kita perlu dibersihkan, tetapi tetap konsisten dengan basis kode, pahami kodenya, dan selektif terhadap abstraksi.
Berikut beberapa tip agar refactoring berhasil:
- Bersikaplah bertahap: lakukan perubahan kecil yang dapat dikelola, bukan perubahan radikal.
- Pahami kode secara mendalam sebelum melakukan pemfaktoran ulang atau abstraksi baru secara signifikan.
- Cocokkan gaya kode yang ada: Konsistensi adalah kunci pemeliharaan.
- Hindari terlalu banyak abstraksi baru: buatlah tetap sederhana, kecuali kompleksitasnya benar-benar dapat dibenarkan.
- Hindari menambahkan perpustakaan baru, terutama perpustakaan dengan gaya pemrograman yang sangat berbeda, tanpa dukungan tim.
- Tulis tes sebelum melakukan refactoring dan perbarui seiring berjalannya waktu. Ini memastikan Anda mempertahankan fungsi aslinya.
- Mintalah kolega Anda bertanggung jawab terhadap prinsip-prinsip ini.
Alat dan teknik untuk pemfaktoran ulang yang lebih baik
Untuk memastikan refactor Anda bermanfaat dan tidak merugikan, pertimbangkan teknik dan alat berikut:
Alat pelapis
Gunakan alat linting untuk menerapkan gaya kode yang konsisten dan menemukan potensi masalah. Lebih cantik dapat membantu memformat otomatis menjadi gaya yang konsisten, sambil tetap ESLint dapat membantu Anda dengan pemeriksaan konsistensi yang lebih bernuansa yang dapat Anda sesuaikan dengan mudah menggunakan plugin Anda sendiri.
Ulasan kode
Terapkan tinjauan kode menyeluruh untuk mendapatkan masukan dari rekan sebelum menggabungkan kode yang telah difaktorkan ulang. Hal ini membantu mendeteksi potensi masalah dengan cepat dan memastikan bahwa kode yang difaktorkan ulang sesuai dengan standar dan harapan tim.
Karangan
Tulis dan jalankan pengujian untuk memastikan kode yang telah difaktorkan ulang tidak merusak fungsionalitas yang sudah ada. Dengan cepat adalah program eksekusi pengujian yang sangat cepat, kuat, dan mudah digunakan yang tidak memerlukan konfigurasi default. Untuk pengujian visual, pertimbangkan untuk menggunakan buku cerita. Perpustakaan Tes Reaksi adalah seperangkat utilitas yang bagus untuk menguji komponen React (ada sudut Dan lagi varian juga).
(Kanan) Alat AI
Biarkan AI membantu Anda dalam upaya pemfaktoran ulang Anda, setidaknya yang mampu menyesuaikan dengan gaya dan konvensi pengkodean yang ada.
Alat yang sangat berguna untuk menjaga konsistensi saat pengkodean antarmuka Kopilot visual. Alat bertenaga AI ini dapat membantu Anda mengubah desain menjadi kode sambil mencocokkan gaya pengkodean yang ada dan memanfaatkan komponen dan token dengan tepat dalam sistem desain Anda.
Kesimpulan
Refactoring adalah bagian penting dari pengembangan perangkat lunak, namun harus dilakukan dengan bijaksana dan memperhatikan basis kode dan dinamika tim yang ada. Tujuan dari pemfaktoran ulang adalah untuk memperbaiki struktur internal kode tanpa mengubah perilaku eksternalnya.
Ingat: refactor terbaik seringkali tidak terlihat oleh pengguna akhir, namun membuat hidup pengembang jauh lebih mudah. Mereka meningkatkan keterbacaan, pemeliharaan, dan efisiensi tanpa mengganggu keseluruhan sistem.
Jika nanti Anda merasa perlu membuat “rencana besar” untuk sebuah kode, ambillah langkah mundur. Pahami sepenuhnya, pikirkan dampak perubahan Anda, dan lakukan perbaikan bertahap yang akan membuat tim Anda berterima kasih.
Diri Anda di masa depan (dan kolega Anda) akan menghargai pendekatan bijaksana untuk menjaga basis kode tetap bersih dan terpelihara.
Video
Oh, apakah kamu juga suka video YouTube? Ada juga video tentang ini, dari Anda sebenarnya:
[ad_2]