From a34d6488a70f556e8ec4a5b1d340e56a6f8464f0 Mon Sep 17 00:00:00 2001 From: novalagung Date: Wed, 27 Nov 2019 22:11:31 +0700 Subject: [PATCH] first commit --- .github/workflows/action.yml | 70 +++ .gitignore | 91 +++ 1-berkenalan-dengan-golang.md | 24 + 10-konstanta.md | 47 ++ 11-operator.md | 98 ++++ 12-seleksi-kondisi.md | 208 +++++++ 13-perulangan.md | 135 +++++ 14-array.md | 202 +++++++ 15-slice.md | 263 +++++++++ 16-map.md | 178 ++++++ 17-fungsi.md | 173 ++++++ 18-fungsi-multiple-return.md | 104 ++++ 19-fungsi-variadic.md | 152 +++++ 2-instalasi-golang.md | 91 +++ 20-fungsi-closure.md | 177 ++++++ 21-fungsi-sebagai-parameter.md | 105 ++++ 22-pointer.md | 106 ++++ 23-struct.md | 415 ++++++++++++++ 24-method.md | 155 ++++++ 25-properti-public-dan-private.md | 330 +++++++++++ 26-interface.md | 182 ++++++ 27-interface-kosong.md | 139 +++++ 28-reflect.md | 171 ++++++ 29-goroutine.md | 92 ++++ 3-gopath-dan-workspace.md | 46 ++ 30-channel.md | 164 ++++++ 31-buffered-channel.md | 59 ++ 32-channel-select.md | 86 +++ 33-channel-range-close.md | 77 +++ 34-channel-timeout.md | 72 +++ 35-defer-exit.md | 136 +++++ 36-error-panic-recover.md | 217 ++++++++ 37-string-format.md | 275 +++++++++ 38-time.md | 219 ++++++++ 39-timer.md | 202 +++++++ 4-instalasi-editor.md | 23 + 40-data-type-conversion.md | 250 +++++++++ 41-strings.md | 163 ++++++ 42-regex.md | 168 ++++++ 43-encoding-base64.md | 88 +++ 44-hash-sha1.md | 103 ++++ 45-command-line-args-flag.md | 137 +++++ 46-exec.md | 58 ++ 47-file.md | 159 ++++++ 48-web.md | 130 +++++ 49-url-parsing.md | 44 ++ 5-go-command.md | 97 ++++ 50-json.md | 137 +++++ 51-web-json-api.md | 135 +++++ 52-http-request.md | 178 ++++++ 53-sql.md | 342 ++++++++++++ 54-mongodb.md | 331 +++++++++++ 55-unit-test.md | 194 +++++++ 56-waitgroup.md | 75 +++ 57-mutex.md | 175 ++++++ 6-hello-world.md | 151 +++++ 7-komentar.md | 52 ++ 8-variabel.md | 188 +++++++ 9-tipe-data.md | 125 +++++ A-58-go-vendoring.md | 82 +++ A-59-go-dep.md | 68 +++ A-60-go-modules.md | 78 +++ A-61-worker-pool.md | 0 B-1-golang-web-hello-world.md | 158 ++++++ B-10-render-html-string.md | 63 +++ B-11-http-method.md | 59 ++ B-12-form-value.md | 137 +++++ B-13-form-upload-file.md | 177 ++++++ B-14-ajax-json-payload.md | 197 +++++++ B-15-ajax-json-response.md | 90 +++ B-16-ajax-multi-upload.md | 205 +++++++ B-17-download-file.md | 225 ++++++++ B-18-http-basic-auth.md | 232 ++++++++ B-19-middleware-using-http-handler.md | 177 ++++++ B-2-routing-http-handlefunc.md | 81 +++ B-20-custom-mux-multiplexer.md | 87 +++ B-21-cookie.md | 131 +++++ B-22-configuration-file.md | 207 +++++++ B-23-server-handle-cancelled-http-request.md | 120 ++++ B-3-routing-static-assets.md | 91 +++ B-4-template-render-html.md | 144 +++++ B-5-template-render-partial-html.md | 234 ++++++++ B-6-template-actions-variables.md | 252 +++++++++ B-7-template-functions.md | 236 ++++++++ B-8-template-custom-functions.md | 113 ++++ B-9-render-specific-html-template.md | 81 +++ C-1-echo-routing.md | 277 ++++++++++ C-10-session.md | 308 +++++++++++ C-12-cors-preflight-request.md | 294 ++++++++++ C-13-csrf.md | 218 ++++++++ C-14-secure-middleware.md | 147 +++++ C-15-http-gzip-compression.md | 139 +++++ C-16-send-email.md | 175 ++++++ C-17-read-write-excel-xlsx-file.md | 190 +++++++ C-18-write-pdf-file.md | 85 +++ C-19-convert-html-to-pdf.md | 170 ++++++ C-2-parsing-http-request-payload-echo.md | 113 ++++ C-20-scraping-parsing-html.md | 188 +++++++ C-21-xml-parser.md | 272 +++++++++ C-22-https-tls.md | 148 +++++ C-23-http2-server-push.md | 160 ++++++ C-24-client-http-request.md | 187 +++++++ C-25-secure-insecure-client-http-request.md | 183 ++++++ C-26-golang-ftp.md | 291 ++++++++++ C-27-golang-ssh-sftp.md | 263 +++++++++ C-28-golang-web-socket.md | 401 ++++++++++++++ C-29-golang-protobuf-implementation.md | 382 +++++++++++++ C-3-http-request-payload-validation.md | 108 ++++ C-30-golang-grpc-protobuf.md | 521 ++++++++++++++++++ C-31-golang-context.md | 325 +++++++++++ C-32-golang-jwt.md | 403 ++++++++++++++ C-33-golang-ldap-authentication.md | 310 +++++++++++ C-34-golang-sso-saml-sp.md | 119 ++++ C-4-http-error-handling.md | 103 ++++ C-5-echo-template-rendering.md | 135 +++++ C-6-advanced-middleware-and-logging.md | 269 +++++++++ C-7-flag-parser.md | 334 +++++++++++ C-8-advanced-configuration-file.md | 145 +++++ C-9-securecookie.md | 151 +++++ CNAME | 1 + CONTRIBUTORS.md | 7 + LICENSE | 437 +++++++++++++++ README.md | 36 ++ SUMMARY.md | 125 +++++ ads.txt | 1 + book.json.template | 42 ++ cover.jpg | Bin 0 -> 353378 bytes cover.psd | Bin 0 -> 4527174 bytes cover_small.jpg | Bin 0 -> 44534 bytes images/A.11_1_operator_comparison.png | Bin 0 -> 26879 bytes images/A.11_2_operator_logical.png | Bin 0 -> 30686 bytes images/A.12_1_if_else.png | Bin 0 -> 23507 bytes images/A.12_2_fallthrough.png | Bin 0 -> 25946 bytes images/A.13_1_for.png | Bin 0 -> 29380 bytes images/A.13_2_for_break_continue.png | Bin 0 -> 27916 bytes images/A.13_3_nested_for.png | Bin 0 -> 27548 bytes images/A.13_4_for_label.png | Bin 0 -> 45899 bytes images/A.14_0_array.png | Bin 0 -> 25318 bytes images/A.14_1_1_array_dots.png | Bin 0 -> 26525 bytes .../A.14_1_array_initialization_and_len.png | Bin 0 -> 27829 bytes images/A.14_2_array_multidimension.png | Bin 0 -> 27026 bytes images/A.14_3_for_range.png | Bin 0 -> 29831 bytes images/A.14_4_for_range_error.png | Bin 0 -> 28470 bytes images/A.14_5_for_range_underscore.png | Bin 0 -> 29737 bytes images/A.15_1_array_index.png | Bin 0 -> 25123 bytes images/A.15_2_slice_reference.png | Bin 0 -> 50546 bytes images/A.16_1_map_set_get.png | Bin 0 -> 25347 bytes images/A.16_2_map_for_range.png | Bin 0 -> 28415 bytes images/A.16_3_map_delete_item.png | Bin 0 -> 30728 bytes images/A.17_1_function.png | Bin 0 -> 24935 bytes images/A.17_2_function_return_type.png | Bin 0 -> 27098 bytes images/A.17_3_function_return_as_break.png | Bin 0 -> 28735 bytes images/A.18_1_multiple_return.png | Bin 0 -> 26874 bytes images/A.19_1_variadic_param.png | Bin 0 -> 24362 bytes images/A.19_2_parameter_combination.png | Bin 0 -> 27775 bytes images/A.1_1_logo.png | Bin 0 -> 29294 bytes images/A.20_1_closure.png | Bin 0 -> 26559 bytes images/A.20_2_iife.png | Bin 0 -> 28542 bytes images/A.20_3_combination.png | Bin 0 -> 31432 bytes images/A.21_1_filtering.png | Bin 0 -> 28356 bytes images/A.22_1_pointer.png | Bin 0 -> 29558 bytes images/A.22_2_pointer_change.png | Bin 0 -> 37706 bytes images/A.22_3_pointer_parameter.png | Bin 0 -> 22708 bytes images/A.23_1_struct.png | Bin 0 -> 23240 bytes images/A.23_2_pointer_object.png | Bin 0 -> 28303 bytes images/A.24_1_method.png | Bin 0 -> 25411 bytes images/A.24_2_method_pointer.png | Bin 0 -> 71852 bytes images/A.25_1_folder_structure.png | Bin 0 -> 43425 bytes images/A.25_2_error.png | Bin 0 -> 28049 bytes images/A.25_2_success.png | Bin 0 -> 23992 bytes images/A.25_3_error.png | Bin 0 -> 28076 bytes images/A.25_4_error.png | Bin 0 -> 32753 bytes images/A.25_4_success.png | Bin 0 -> 26656 bytes images/A.25_5_structure.png | Bin 0 -> 41894 bytes images/A.25_6_multi_main.png | Bin 0 -> 22469 bytes images/A.25_7_init.png | Bin 0 -> 25652 bytes images/A.26_1_interface.png | Bin 0 -> 39903 bytes images/A.26_2_embedded_interface.png | Bin 0 -> 28003 bytes images/A.27_1_empty_interface.png | Bin 0 -> 27076 bytes images/A.27_2_interface_casting.png | Bin 0 -> 26801 bytes images/A.27_3_interface_pointer.png | Bin 0 -> 23002 bytes images/A.28_0_reflect.png | Bin 0 -> 22604 bytes images/A.28_1_accessing_properties.png | Bin 0 -> 29345 bytes .../A.28_2_accessing_method_information.png | Bin 0 -> 24956 bytes images/A.29_1_goroutine.png | Bin 0 -> 51371 bytes images/A.30_1_analogy.png | Bin 0 -> 59935 bytes images/A.30_2_channel.png | Bin 0 -> 32962 bytes images/A.30_3_channel_param.png | Bin 0 -> 32082 bytes images/A.31_1_anatomy.png | Bin 0 -> 47597 bytes images/A.31_2_buffered_channel.png | Bin 0 -> 42969 bytes images/A.32_1_channel_select.png | Bin 0 -> 31775 bytes images/A.33_1_for_range_close.png | Bin 0 -> 36816 bytes images/A.34_1_channel_delay.png | Bin 0 -> 54801 bytes images/A.35_1_defer.png | Bin 0 -> 22499 bytes images/A.35_2_defer_return.png | Bin 0 -> 64240 bytes images/A.35_3_exit.png | Bin 0 -> 21829 bytes images/A.36_1_error.png | Bin 0 -> 36192 bytes images/A.36_2_custom_error.png | Bin 0 -> 32062 bytes images/A.36_3_panic.png | Bin 0 -> 40788 bytes images/A.36_4_recover.png | Bin 0 -> 95169 bytes images/A.38_1_time_instance.png | Bin 0 -> 26724 bytes images/A.38_2_time_parse.png | Bin 0 -> 30960 bytes images/A.38_3_time_format.png | Bin 0 -> 30887 bytes images/A.38_4_error_parse.png | Bin 0 -> 28173 bytes images/A.39_1_timer.png | Bin 0 -> 42312 bytes images/A.3_1_path.png | Bin 0 -> 49942 bytes images/A.3_2_workspace.png | Bin 0 -> 21555 bytes images/A.43_1_encode_decode.png | Bin 0 -> 28821 bytes images/A.44_1_hash_sha1.png | Bin 0 -> 27243 bytes images/A.44_2_hash_salt_sha1.png | Bin 0 -> 43835 bytes images/A.45_1_argument.png | Bin 0 -> 50823 bytes images/A.45_2_flag.png | Bin 0 -> 33818 bytes images/A.45_3_flag_info.png | Bin 0 -> 41385 bytes images/A.46_1_exec.png | Bin 0 -> 30031 bytes images/A.47_1_create.png | Bin 0 -> 47798 bytes images/A.47_2_write.png | Bin 0 -> 53567 bytes images/A.47_3_read.png | Bin 0 -> 29747 bytes images/A.47_4_delete.png | Bin 0 -> 48356 bytes images/A.48_0_start_server.png | Bin 0 -> 23370 bytes images/A.48_1_web.png | Bin 0 -> 71465 bytes images/A.48_2_template.png | Bin 0 -> 36831 bytes images/A.49_1_parse_url.png | Bin 0 -> 30347 bytes images/A.4_1_visual_studio_code.png | Bin 0 -> 58975 bytes images/A.4_2_vscode_go_extension.png | Bin 0 -> 135257 bytes images/A.50_1_decode.png | Bin 0 -> 24199 bytes images/A.50_2_encode.png | Bin 0 -> 25625 bytes images/A.51_1_server.png | Bin 0 -> 22407 bytes images/A.51_2_test_api_users.png | Bin 0 -> 55586 bytes images/A.51_3_test_api_user.png | Bin 0 -> 38691 bytes images/A.52_1_http_request.png | Bin 0 -> 29797 bytes images/A.52_2_http_request_form_data.png | Bin 0 -> 23107 bytes images/A.53_1_go_get_driver.png | Bin 0 -> 47241 bytes images/A.53_2_sql_query.png | Bin 0 -> 23526 bytes images/A.53_3_sql_query_row.png | Bin 0 -> 22843 bytes images/A.53_4_prepared_statement.png | Bin 0 -> 30743 bytes images/A.54_2_insert.png | Bin 0 -> 86136 bytes images/A.54_3_find.png | Bin 0 -> 24231 bytes images/A.54_4_update.png | Bin 0 -> 90529 bytes images/A.54_5_remove.png | Bin 0 -> 55444 bytes images/A.55_1_test.png | Bin 0 -> 46278 bytes images/A.55_2_test_fail.png | Bin 0 -> 49684 bytes images/A.55_3_benchmark.png | Bin 0 -> 31076 bytes images/A.55_4_testify.png | Bin 0 -> 48745 bytes images/A.56_1_waitgroup.png | Bin 0 -> 33896 bytes images/A.57_1_race_condition.png | Bin 0 -> 31605 bytes images/A.57_2_race_detector.png | Bin 0 -> 63043 bytes images/A.57_3_mutex.png | Bin 0 -> 33782 bytes images/A.58_1_vendor_vs_nope.png | Bin 0 -> 50261 bytes images/A.5_1_go_run.png | Bin 0 -> 27864 bytes images/A.5_2_go_run_multi.png | Bin 0 -> 25812 bytes images/A.5_3_go_test.png | Bin 0 -> 24535 bytes images/A.5_4_go_build.png | Bin 0 -> 27022 bytes images/A.5_6_go_get.png | Bin 0 -> 45207 bytes images/A.5_7_go_install.png | Bin 0 -> 43200 bytes images/A.60_1_import_error.png | Bin 0 -> 75166 bytes images/A.60_2_go_mod.png | Bin 0 -> 56380 bytes images/A.60_3_module_name_1.png | Bin 0 -> 60142 bytes images/A.60_4_module_name_2.png | Bin 0 -> 73326 bytes images/A.60_5_mod_vendor.png | Bin 0 -> 78109 bytes images/A.6_1_editor_project_explorer.png | Bin 0 -> 5281 bytes images/A.6_2_new_project_on_editor.png | Bin 0 -> 9778 bytes images/A.6_3_new_file_on_editor.png | Bin 0 -> 14451 bytes images/A.6_4_execute_hello_world.png | Bin 0 -> 17452 bytes images/A.7_1_inline_comment.png | Bin 0 -> 22504 bytes images/A.8_1_variabel.png | Bin 0 -> 25957 bytes images/A.8_2_unused_variabel.png | Bin 0 -> 29385 bytes images/A.9_1_decimal_data_type.png | Bin 0 -> 28790 bytes images/A.9_2_unescaped_string.png | Bin 0 -> 31103 bytes images/B.10_1_parse.png | Bin 0 -> 13201 bytes images/B.11_1_get.png | Bin 0 -> 18904 bytes images/B.11_2_post.png | Bin 0 -> 19166 bytes images/B.11_3_bad_request.png | Bin 0 -> 28076 bytes images/B.12_1_form.png | Bin 0 -> 35548 bytes images/B.13_1_structure.png | Bin 0 -> 11252 bytes images/B.13_2_files.png | Bin 0 -> 79994 bytes images/B.14_1_structure.png | Bin 0 -> 16443 bytes images/B.14_2_test.png | Bin 0 -> 20250 bytes images/B.14_3_inspect.png | Bin 0 -> 61343 bytes images/B.15_1_test.png | Bin 0 -> 23173 bytes images/B.16_1_structure.png | Bin 0 -> 19181 bytes images/B.16_2_upload_files.png | Bin 0 -> 71405 bytes images/B.16_3_uploaded_files.png | Bin 0 -> 29196 bytes images/B.17_1_structure.png | Bin 0 -> 14859 bytes images/B.17_2_download.png | Bin 0 -> 28840 bytes images/B.18_1_structure.png | Bin 0 -> 7620 bytes images/B.18_2_run_server.png | Bin 0 -> 5470 bytes images/B.18_3_test_api.png | Bin 0 -> 19747 bytes images/B.1_1_start_server.png | Bin 0 -> 5173 bytes images/B.1_2_browse.png | Bin 0 -> 17239 bytes images/B.21_1_cookie.png | Bin 0 -> 63010 bytes images/B.22_1_structure.png | Bin 0 -> 6400 bytes images/B.22_2_log.png | Bin 0 -> 14409 bytes images/B.23_1_cancelled_request_get.png | Bin 0 -> 29484 bytes .../B.23_2_cancelled request_with_payload.png | Bin 0 -> 32414 bytes images/B.2_1_routing.png | Bin 0 -> 16541 bytes images/B.3_1_structure.png | Bin 0 -> 13944 bytes images/B.3_2_preview.png | Bin 0 -> 16235 bytes images/B.4_1_structure.png | Bin 0 -> 19867 bytes images/B.4_2_output.png | Bin 0 -> 23598 bytes images/B.4_3_static_route.png | Bin 0 -> 23948 bytes images/B.5_1_structure.png | Bin 0 -> 22245 bytes images/B.5_2_routes.png | Bin 0 -> 20003 bytes images/B.6_1_pipeline.png | Bin 0 -> 14181 bytes images/B.6_2_variable.png | Bin 0 -> 15241 bytes images/B.6_3_loop.png | Bin 0 -> 18929 bytes images/B.6_4_property.png | Bin 0 -> 21876 bytes images/B.6_5_method.png | Bin 0 -> 23299 bytes images/B.6_6_with.png | Bin 0 -> 25804 bytes images/B.6_7_if.png | Bin 0 -> 27218 bytes images/B.7_1_escape_html.png | Bin 0 -> 14465 bytes images/B.7_2_cond.png | Bin 0 -> 18112 bytes images/B.7_3_method.png | Bin 0 -> 20342 bytes images/B.7_4_string_func.png | Bin 0 -> 24446 bytes images/B.7_5_len_index.png | Bin 0 -> 29513 bytes images/B.7_6_or_and_not.png | Bin 0 -> 31336 bytes images/B.8_1_func.png | Bin 0 -> 21295 bytes images/B.9_1_preview.png | Bin 0 -> 14542 bytes images/C.10_1_test.png | Bin 0 -> 41763 bytes .../C.12_1_cors_from_google_to_localhost.png | Bin 0 -> 137177 bytes ...12_2_cors_from_novalagung_to_localhost.png | Bin 0 -> 64075 bytes images/C.12_3_cors_multiple_domain.png | Bin 0 -> 35937 bytes images/C.12_4_preflight_allowed.png | Bin 0 -> 72081 bytes images/C.12_5_multi_domain.png | Bin 0 -> 30195 bytes images/C.13_1_csrf.png | Bin 0 -> 16800 bytes images/C.13_2_csrf_curl.png | Bin 0 -> 26309 bytes images/C.15_1_without_compression.png | Bin 0 -> 36493 bytes images/C.15_2_with_compression.png | Bin 0 -> 74677 bytes images/C.16_1_gmail_error.png | Bin 0 -> 45976 bytes images/C.16_2_less_secure.png | Bin 0 -> 34699 bytes images/C.16_3_email_received.png | Bin 0 -> 17457 bytes images/C.16_4_mail_with_attachment.png | Bin 0 -> 32410 bytes images/C.17_1_create_excel.png | Bin 0 -> 53287 bytes images/C.17_2_new_sheet_style_merge_cell.png | Bin 0 -> 45556 bytes images/C.17_3_read_excel.png | Bin 0 -> 18877 bytes images/C.18_1_write_pdf_file.png | Bin 0 -> 35573 bytes images/C.19_1_convert_html_to_pdf.png | Bin 0 -> 173775 bytes images/C.1_1_routing_slash_test.png | Bin 0 -> 5723 bytes images/C.1_2_routing_static_assets.png | Bin 0 -> 7373 bytes images/C.20_1_novalagung.png | Bin 0 -> 669690 bytes images/C.20_2_structure.png | Bin 0 -> 67262 bytes images/C.20_3_output.png | Bin 0 -> 59531 bytes images/C.20_4_beautified_html.png | Bin 0 -> 32113 bytes images/C.21_1_json_from_xml.png | Bin 0 -> 40732 bytes images/C.21_2_find.png | Bin 0 -> 10976 bytes images/C.21_3_generated_xml.png | Bin 0 -> 51173 bytes images/C.22_1.1_public_and_private_key.png | Bin 0 -> 58804 bytes images/C.22_1_public_and_private_key.png | Bin 0 -> 24365 bytes images/C.22_2_structure.png | Bin 0 -> 6865 bytes images/C.22_3_curl_example.png | Bin 0 -> 17761 bytes images/C.22_4_browser_example.png | Bin 0 -> 46943 bytes images/C.23_1_spdy_checker.png | Bin 0 -> 28933 bytes images/C.23_2_spdy_indicator.png | Bin 0 -> 61784 bytes images/C.24_1_test_client_request.png | Bin 0 -> 50203 bytes ...http_request_to_ssl_enabled_web_server.png | Bin 0 -> 60259 bytes images/C.25_2_insecure_client_request.png | Bin 0 -> 46067 bytes images/C.25_3_secure_client_request.png | Bin 0 -> 54437 bytes images/C.26_1_ftp_folder_structure.png | Bin 0 -> 20024 bytes images/C.26_2_list_assets.png | Bin 0 -> 34330 bytes images/C.26_3_list_assets_subfolder.png | Bin 0 -> 41224 bytes images/C.26_4_read_file.png | Bin 0 -> 47124 bytes images/C.26_5_download_file.png | Bin 0 -> 63901 bytes images/C.26_6_movie_preview.png | Bin 0 -> 150046 bytes images/C.26_7_upload_file.png | Bin 0 -> 43521 bytes images/C.27_1_ls_testing.png | Bin 0 -> 24874 bytes images/C.27_2_multiple_command.png | Bin 0 -> 26520 bytes images/C.27_3_sftp.png | Bin 0 -> 41346 bytes images/C.28_1_template.png | Bin 0 -> 23346 bytes images/C.28_2_prompt_username.png | Bin 0 -> 33578 bytes images/C.28_3_chatting.png | Bin 0 -> 230238 bytes images/C.28_4_user_leave.png | Bin 0 -> 97106 bytes images/C.29_1_http_json_analogy.png | Bin 0 -> 11768 bytes images/C.29_2_grpc_protobuf_analogy.png | Bin 0 -> 12488 bytes images/C.29_3_print.png | Bin 0 -> 25447 bytes images/C.29_4_print_json.png | Bin 0 -> 32651 bytes images/C.29_5_unmarshal.png | Bin 0 -> 36393 bytes images/C.30_1_grpc_test1.png | Bin 0 -> 53351 bytes images/C.30_2_grpc_test2.png | Bin 0 -> 62328 bytes images/C.30_3_grpc_test3.png | Bin 0 -> 91106 bytes images/C.31_1_context_example.png | Bin 0 -> 29122 bytes images/C.31_2_etc_hosts.png | Bin 0 -> 15531 bytes images/C.31_3_result_ok.png | Bin 0 -> 121047 bytes images/C.31_4_result_fail.png | Bin 0 -> 60088 bytes images/C.31_5_api_hostname.png | Bin 0 -> 129232 bytes images/C.32_1_jwt_scheme.png | Bin 0 -> 46771 bytes images/C.32_2_jwt_authentication.png | Bin 0 -> 30508 bytes images/C.32_3_jwt_authorization.png | Bin 0 -> 30621 bytes images/C.32_4_invalid_jwt_token.png | Bin 0 -> 60844 bytes images/C.33_1_ldap_browse.png | Bin 0 -> 135693 bytes images/C.33_2_ldap_login.png | Bin 0 -> 63709 bytes images/C.33_3_ldap_login_success.png | Bin 0 -> 19734 bytes images/C.3_1_validation.png | Bin 0 -> 59676 bytes images/C.4_1_simple_error_handler.png | Bin 0 -> 26133 bytes images/C.4_2_cli_error.png | Bin 0 -> 24982 bytes images/C.4_3_advance_handler.png | Bin 0 -> 62178 bytes images/C.5_1_preview.png | Bin 0 -> 8836 bytes images/C.6_1_middleware_log.png | Bin 0 -> 18106 bytes images/C.6_2_middleware_logging.png | Bin 0 -> 21851 bytes images/C.6_3_middleware_logrus.png | Bin 0 -> 53577 bytes images/C.7_1_arg.png | Bin 0 -> 58339 bytes images/C.7_2_flag.png | Bin 0 -> 58840 bytes images/C.7_3_command_help.png | Bin 0 -> 33281 bytes images/C.7_4_command_help_full.png | Bin 0 -> 42657 bytes images/C.8_1_app.png | Bin 0 -> 15948 bytes images/C.9_1_securecookie.png | Bin 0 -> 9674 bytes images/C.9_2_cookie_header.png | Bin 0 -> 60910 bytes images/cover_fb_share.jpg | Bin 0 -> 112670 bytes website-adjustment.go | 101 ++++ website.css | 91 +++ website.js | 25 + 409 files changed, 20869 insertions(+) create mode 100644 .github/workflows/action.yml create mode 100644 .gitignore create mode 100644 1-berkenalan-dengan-golang.md create mode 100644 10-konstanta.md create mode 100644 11-operator.md create mode 100644 12-seleksi-kondisi.md create mode 100644 13-perulangan.md create mode 100644 14-array.md create mode 100644 15-slice.md create mode 100644 16-map.md create mode 100644 17-fungsi.md create mode 100644 18-fungsi-multiple-return.md create mode 100644 19-fungsi-variadic.md create mode 100644 2-instalasi-golang.md create mode 100644 20-fungsi-closure.md create mode 100644 21-fungsi-sebagai-parameter.md create mode 100644 22-pointer.md create mode 100644 23-struct.md create mode 100644 24-method.md create mode 100644 25-properti-public-dan-private.md create mode 100644 26-interface.md create mode 100644 27-interface-kosong.md create mode 100644 28-reflect.md create mode 100644 29-goroutine.md create mode 100644 3-gopath-dan-workspace.md create mode 100644 30-channel.md create mode 100644 31-buffered-channel.md create mode 100644 32-channel-select.md create mode 100644 33-channel-range-close.md create mode 100644 34-channel-timeout.md create mode 100644 35-defer-exit.md create mode 100644 36-error-panic-recover.md create mode 100644 37-string-format.md create mode 100644 38-time.md create mode 100644 39-timer.md create mode 100644 4-instalasi-editor.md create mode 100644 40-data-type-conversion.md create mode 100644 41-strings.md create mode 100644 42-regex.md create mode 100644 43-encoding-base64.md create mode 100644 44-hash-sha1.md create mode 100644 45-command-line-args-flag.md create mode 100644 46-exec.md create mode 100644 47-file.md create mode 100644 48-web.md create mode 100644 49-url-parsing.md create mode 100644 5-go-command.md create mode 100644 50-json.md create mode 100644 51-web-json-api.md create mode 100644 52-http-request.md create mode 100644 53-sql.md create mode 100644 54-mongodb.md create mode 100644 55-unit-test.md create mode 100644 56-waitgroup.md create mode 100644 57-mutex.md create mode 100644 6-hello-world.md create mode 100644 7-komentar.md create mode 100644 8-variabel.md create mode 100644 9-tipe-data.md create mode 100644 A-58-go-vendoring.md create mode 100644 A-59-go-dep.md create mode 100644 A-60-go-modules.md create mode 100644 A-61-worker-pool.md create mode 100644 B-1-golang-web-hello-world.md create mode 100644 B-10-render-html-string.md create mode 100644 B-11-http-method.md create mode 100644 B-12-form-value.md create mode 100644 B-13-form-upload-file.md create mode 100644 B-14-ajax-json-payload.md create mode 100644 B-15-ajax-json-response.md create mode 100644 B-16-ajax-multi-upload.md create mode 100644 B-17-download-file.md create mode 100644 B-18-http-basic-auth.md create mode 100644 B-19-middleware-using-http-handler.md create mode 100644 B-2-routing-http-handlefunc.md create mode 100644 B-20-custom-mux-multiplexer.md create mode 100644 B-21-cookie.md create mode 100644 B-22-configuration-file.md create mode 100644 B-23-server-handle-cancelled-http-request.md create mode 100644 B-3-routing-static-assets.md create mode 100644 B-4-template-render-html.md create mode 100644 B-5-template-render-partial-html.md create mode 100644 B-6-template-actions-variables.md create mode 100644 B-7-template-functions.md create mode 100644 B-8-template-custom-functions.md create mode 100644 B-9-render-specific-html-template.md create mode 100644 C-1-echo-routing.md create mode 100644 C-10-session.md create mode 100644 C-12-cors-preflight-request.md create mode 100644 C-13-csrf.md create mode 100644 C-14-secure-middleware.md create mode 100644 C-15-http-gzip-compression.md create mode 100644 C-16-send-email.md create mode 100644 C-17-read-write-excel-xlsx-file.md create mode 100644 C-18-write-pdf-file.md create mode 100644 C-19-convert-html-to-pdf.md create mode 100644 C-2-parsing-http-request-payload-echo.md create mode 100644 C-20-scraping-parsing-html.md create mode 100644 C-21-xml-parser.md create mode 100644 C-22-https-tls.md create mode 100644 C-23-http2-server-push.md create mode 100644 C-24-client-http-request.md create mode 100644 C-25-secure-insecure-client-http-request.md create mode 100644 C-26-golang-ftp.md create mode 100644 C-27-golang-ssh-sftp.md create mode 100644 C-28-golang-web-socket.md create mode 100644 C-29-golang-protobuf-implementation.md create mode 100644 C-3-http-request-payload-validation.md create mode 100644 C-30-golang-grpc-protobuf.md create mode 100644 C-31-golang-context.md create mode 100644 C-32-golang-jwt.md create mode 100644 C-33-golang-ldap-authentication.md create mode 100644 C-34-golang-sso-saml-sp.md create mode 100644 C-4-http-error-handling.md create mode 100644 C-5-echo-template-rendering.md create mode 100644 C-6-advanced-middleware-and-logging.md create mode 100644 C-7-flag-parser.md create mode 100644 C-8-advanced-configuration-file.md create mode 100644 C-9-securecookie.md create mode 100644 CNAME create mode 100644 CONTRIBUTORS.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SUMMARY.md create mode 100644 ads.txt create mode 100644 book.json.template create mode 100644 cover.jpg create mode 100644 cover.psd create mode 100644 cover_small.jpg create mode 100644 images/A.11_1_operator_comparison.png create mode 100644 images/A.11_2_operator_logical.png create mode 100644 images/A.12_1_if_else.png create mode 100644 images/A.12_2_fallthrough.png create mode 100644 images/A.13_1_for.png create mode 100644 images/A.13_2_for_break_continue.png create mode 100644 images/A.13_3_nested_for.png create mode 100644 images/A.13_4_for_label.png create mode 100644 images/A.14_0_array.png create mode 100644 images/A.14_1_1_array_dots.png create mode 100644 images/A.14_1_array_initialization_and_len.png create mode 100644 images/A.14_2_array_multidimension.png create mode 100644 images/A.14_3_for_range.png create mode 100644 images/A.14_4_for_range_error.png create mode 100644 images/A.14_5_for_range_underscore.png create mode 100644 images/A.15_1_array_index.png create mode 100644 images/A.15_2_slice_reference.png create mode 100644 images/A.16_1_map_set_get.png create mode 100644 images/A.16_2_map_for_range.png create mode 100644 images/A.16_3_map_delete_item.png create mode 100644 images/A.17_1_function.png create mode 100644 images/A.17_2_function_return_type.png create mode 100644 images/A.17_3_function_return_as_break.png create mode 100644 images/A.18_1_multiple_return.png create mode 100644 images/A.19_1_variadic_param.png create mode 100644 images/A.19_2_parameter_combination.png create mode 100644 images/A.1_1_logo.png create mode 100644 images/A.20_1_closure.png create mode 100644 images/A.20_2_iife.png create mode 100644 images/A.20_3_combination.png create mode 100644 images/A.21_1_filtering.png create mode 100644 images/A.22_1_pointer.png create mode 100644 images/A.22_2_pointer_change.png create mode 100644 images/A.22_3_pointer_parameter.png create mode 100644 images/A.23_1_struct.png create mode 100644 images/A.23_2_pointer_object.png create mode 100644 images/A.24_1_method.png create mode 100644 images/A.24_2_method_pointer.png create mode 100644 images/A.25_1_folder_structure.png create mode 100644 images/A.25_2_error.png create mode 100644 images/A.25_2_success.png create mode 100644 images/A.25_3_error.png create mode 100644 images/A.25_4_error.png create mode 100644 images/A.25_4_success.png create mode 100644 images/A.25_5_structure.png create mode 100644 images/A.25_6_multi_main.png create mode 100644 images/A.25_7_init.png create mode 100644 images/A.26_1_interface.png create mode 100644 images/A.26_2_embedded_interface.png create mode 100644 images/A.27_1_empty_interface.png create mode 100644 images/A.27_2_interface_casting.png create mode 100644 images/A.27_3_interface_pointer.png create mode 100644 images/A.28_0_reflect.png create mode 100644 images/A.28_1_accessing_properties.png create mode 100644 images/A.28_2_accessing_method_information.png create mode 100644 images/A.29_1_goroutine.png create mode 100644 images/A.30_1_analogy.png create mode 100644 images/A.30_2_channel.png create mode 100644 images/A.30_3_channel_param.png create mode 100644 images/A.31_1_anatomy.png create mode 100644 images/A.31_2_buffered_channel.png create mode 100644 images/A.32_1_channel_select.png create mode 100644 images/A.33_1_for_range_close.png create mode 100644 images/A.34_1_channel_delay.png create mode 100644 images/A.35_1_defer.png create mode 100644 images/A.35_2_defer_return.png create mode 100644 images/A.35_3_exit.png create mode 100644 images/A.36_1_error.png create mode 100644 images/A.36_2_custom_error.png create mode 100644 images/A.36_3_panic.png create mode 100644 images/A.36_4_recover.png create mode 100644 images/A.38_1_time_instance.png create mode 100644 images/A.38_2_time_parse.png create mode 100644 images/A.38_3_time_format.png create mode 100644 images/A.38_4_error_parse.png create mode 100644 images/A.39_1_timer.png create mode 100644 images/A.3_1_path.png create mode 100644 images/A.3_2_workspace.png create mode 100644 images/A.43_1_encode_decode.png create mode 100644 images/A.44_1_hash_sha1.png create mode 100644 images/A.44_2_hash_salt_sha1.png create mode 100644 images/A.45_1_argument.png create mode 100644 images/A.45_2_flag.png create mode 100644 images/A.45_3_flag_info.png create mode 100644 images/A.46_1_exec.png create mode 100644 images/A.47_1_create.png create mode 100644 images/A.47_2_write.png create mode 100644 images/A.47_3_read.png create mode 100644 images/A.47_4_delete.png create mode 100644 images/A.48_0_start_server.png create mode 100644 images/A.48_1_web.png create mode 100644 images/A.48_2_template.png create mode 100644 images/A.49_1_parse_url.png create mode 100644 images/A.4_1_visual_studio_code.png create mode 100644 images/A.4_2_vscode_go_extension.png create mode 100644 images/A.50_1_decode.png create mode 100644 images/A.50_2_encode.png create mode 100644 images/A.51_1_server.png create mode 100644 images/A.51_2_test_api_users.png create mode 100644 images/A.51_3_test_api_user.png create mode 100644 images/A.52_1_http_request.png create mode 100644 images/A.52_2_http_request_form_data.png create mode 100644 images/A.53_1_go_get_driver.png create mode 100644 images/A.53_2_sql_query.png create mode 100644 images/A.53_3_sql_query_row.png create mode 100644 images/A.53_4_prepared_statement.png create mode 100644 images/A.54_2_insert.png create mode 100644 images/A.54_3_find.png create mode 100644 images/A.54_4_update.png create mode 100644 images/A.54_5_remove.png create mode 100644 images/A.55_1_test.png create mode 100644 images/A.55_2_test_fail.png create mode 100644 images/A.55_3_benchmark.png create mode 100644 images/A.55_4_testify.png create mode 100644 images/A.56_1_waitgroup.png create mode 100644 images/A.57_1_race_condition.png create mode 100644 images/A.57_2_race_detector.png create mode 100644 images/A.57_3_mutex.png create mode 100644 images/A.58_1_vendor_vs_nope.png create mode 100644 images/A.5_1_go_run.png create mode 100644 images/A.5_2_go_run_multi.png create mode 100644 images/A.5_3_go_test.png create mode 100644 images/A.5_4_go_build.png create mode 100644 images/A.5_6_go_get.png create mode 100644 images/A.5_7_go_install.png create mode 100644 images/A.60_1_import_error.png create mode 100644 images/A.60_2_go_mod.png create mode 100644 images/A.60_3_module_name_1.png create mode 100644 images/A.60_4_module_name_2.png create mode 100644 images/A.60_5_mod_vendor.png create mode 100644 images/A.6_1_editor_project_explorer.png create mode 100644 images/A.6_2_new_project_on_editor.png create mode 100644 images/A.6_3_new_file_on_editor.png create mode 100644 images/A.6_4_execute_hello_world.png create mode 100644 images/A.7_1_inline_comment.png create mode 100644 images/A.8_1_variabel.png create mode 100644 images/A.8_2_unused_variabel.png create mode 100644 images/A.9_1_decimal_data_type.png create mode 100644 images/A.9_2_unescaped_string.png create mode 100644 images/B.10_1_parse.png create mode 100644 images/B.11_1_get.png create mode 100644 images/B.11_2_post.png create mode 100644 images/B.11_3_bad_request.png create mode 100644 images/B.12_1_form.png create mode 100644 images/B.13_1_structure.png create mode 100644 images/B.13_2_files.png create mode 100644 images/B.14_1_structure.png create mode 100644 images/B.14_2_test.png create mode 100644 images/B.14_3_inspect.png create mode 100644 images/B.15_1_test.png create mode 100644 images/B.16_1_structure.png create mode 100644 images/B.16_2_upload_files.png create mode 100644 images/B.16_3_uploaded_files.png create mode 100644 images/B.17_1_structure.png create mode 100644 images/B.17_2_download.png create mode 100644 images/B.18_1_structure.png create mode 100644 images/B.18_2_run_server.png create mode 100644 images/B.18_3_test_api.png create mode 100644 images/B.1_1_start_server.png create mode 100644 images/B.1_2_browse.png create mode 100644 images/B.21_1_cookie.png create mode 100644 images/B.22_1_structure.png create mode 100644 images/B.22_2_log.png create mode 100644 images/B.23_1_cancelled_request_get.png create mode 100644 images/B.23_2_cancelled request_with_payload.png create mode 100644 images/B.2_1_routing.png create mode 100644 images/B.3_1_structure.png create mode 100644 images/B.3_2_preview.png create mode 100644 images/B.4_1_structure.png create mode 100644 images/B.4_2_output.png create mode 100644 images/B.4_3_static_route.png create mode 100644 images/B.5_1_structure.png create mode 100644 images/B.5_2_routes.png create mode 100644 images/B.6_1_pipeline.png create mode 100644 images/B.6_2_variable.png create mode 100644 images/B.6_3_loop.png create mode 100644 images/B.6_4_property.png create mode 100644 images/B.6_5_method.png create mode 100644 images/B.6_6_with.png create mode 100644 images/B.6_7_if.png create mode 100644 images/B.7_1_escape_html.png create mode 100644 images/B.7_2_cond.png create mode 100644 images/B.7_3_method.png create mode 100644 images/B.7_4_string_func.png create mode 100644 images/B.7_5_len_index.png create mode 100644 images/B.7_6_or_and_not.png create mode 100644 images/B.8_1_func.png create mode 100644 images/B.9_1_preview.png create mode 100644 images/C.10_1_test.png create mode 100644 images/C.12_1_cors_from_google_to_localhost.png create mode 100644 images/C.12_2_cors_from_novalagung_to_localhost.png create mode 100644 images/C.12_3_cors_multiple_domain.png create mode 100644 images/C.12_4_preflight_allowed.png create mode 100644 images/C.12_5_multi_domain.png create mode 100644 images/C.13_1_csrf.png create mode 100644 images/C.13_2_csrf_curl.png create mode 100644 images/C.15_1_without_compression.png create mode 100644 images/C.15_2_with_compression.png create mode 100644 images/C.16_1_gmail_error.png create mode 100644 images/C.16_2_less_secure.png create mode 100644 images/C.16_3_email_received.png create mode 100644 images/C.16_4_mail_with_attachment.png create mode 100644 images/C.17_1_create_excel.png create mode 100644 images/C.17_2_new_sheet_style_merge_cell.png create mode 100644 images/C.17_3_read_excel.png create mode 100644 images/C.18_1_write_pdf_file.png create mode 100644 images/C.19_1_convert_html_to_pdf.png create mode 100644 images/C.1_1_routing_slash_test.png create mode 100644 images/C.1_2_routing_static_assets.png create mode 100644 images/C.20_1_novalagung.png create mode 100644 images/C.20_2_structure.png create mode 100644 images/C.20_3_output.png create mode 100644 images/C.20_4_beautified_html.png create mode 100644 images/C.21_1_json_from_xml.png create mode 100644 images/C.21_2_find.png create mode 100644 images/C.21_3_generated_xml.png create mode 100644 images/C.22_1.1_public_and_private_key.png create mode 100644 images/C.22_1_public_and_private_key.png create mode 100644 images/C.22_2_structure.png create mode 100644 images/C.22_3_curl_example.png create mode 100644 images/C.22_4_browser_example.png create mode 100644 images/C.23_1_spdy_checker.png create mode 100644 images/C.23_2_spdy_indicator.png create mode 100644 images/C.24_1_test_client_request.png create mode 100644 images/C.25_1_http_request_to_ssl_enabled_web_server.png create mode 100644 images/C.25_2_insecure_client_request.png create mode 100644 images/C.25_3_secure_client_request.png create mode 100644 images/C.26_1_ftp_folder_structure.png create mode 100644 images/C.26_2_list_assets.png create mode 100644 images/C.26_3_list_assets_subfolder.png create mode 100644 images/C.26_4_read_file.png create mode 100644 images/C.26_5_download_file.png create mode 100644 images/C.26_6_movie_preview.png create mode 100644 images/C.26_7_upload_file.png create mode 100644 images/C.27_1_ls_testing.png create mode 100644 images/C.27_2_multiple_command.png create mode 100644 images/C.27_3_sftp.png create mode 100644 images/C.28_1_template.png create mode 100644 images/C.28_2_prompt_username.png create mode 100644 images/C.28_3_chatting.png create mode 100644 images/C.28_4_user_leave.png create mode 100644 images/C.29_1_http_json_analogy.png create mode 100644 images/C.29_2_grpc_protobuf_analogy.png create mode 100644 images/C.29_3_print.png create mode 100644 images/C.29_4_print_json.png create mode 100644 images/C.29_5_unmarshal.png create mode 100644 images/C.30_1_grpc_test1.png create mode 100644 images/C.30_2_grpc_test2.png create mode 100644 images/C.30_3_grpc_test3.png create mode 100644 images/C.31_1_context_example.png create mode 100644 images/C.31_2_etc_hosts.png create mode 100644 images/C.31_3_result_ok.png create mode 100644 images/C.31_4_result_fail.png create mode 100644 images/C.31_5_api_hostname.png create mode 100644 images/C.32_1_jwt_scheme.png create mode 100644 images/C.32_2_jwt_authentication.png create mode 100644 images/C.32_3_jwt_authorization.png create mode 100644 images/C.32_4_invalid_jwt_token.png create mode 100644 images/C.33_1_ldap_browse.png create mode 100644 images/C.33_2_ldap_login.png create mode 100644 images/C.33_3_ldap_login_success.png create mode 100644 images/C.3_1_validation.png create mode 100644 images/C.4_1_simple_error_handler.png create mode 100644 images/C.4_2_cli_error.png create mode 100644 images/C.4_3_advance_handler.png create mode 100644 images/C.5_1_preview.png create mode 100644 images/C.6_1_middleware_log.png create mode 100644 images/C.6_2_middleware_logging.png create mode 100644 images/C.6_3_middleware_logrus.png create mode 100644 images/C.7_1_arg.png create mode 100644 images/C.7_2_flag.png create mode 100644 images/C.7_3_command_help.png create mode 100644 images/C.7_4_command_help_full.png create mode 100644 images/C.8_1_app.png create mode 100644 images/C.9_1_securecookie.png create mode 100644 images/C.9_2_cookie_header.png create mode 100644 images/cover_fb_share.jpg create mode 100644 website-adjustment.go create mode 100644 website.css create mode 100644 website.js diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml new file mode 100644 index 000000000..06bbc23e0 --- /dev/null +++ b/.github/workflows/action.yml @@ -0,0 +1,70 @@ +name: Update website + +on: + push: + branches: + - master + +jobs: + job_deploy_website: + name: 'Deploy website' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: '10.x' + - uses: actions/setup-go@v1 + with: + go-version: 1.13 + - name: 'Installing gitbook cli' + run: npm install -g gitbook-cli + - name: 'Generating distributable files' + run: | + cp book.json.template book.json + gitbook install + gitbook build + go run website-adjustment.go + cd _book + rm -rf LICENSE + rm -rf book* + rm -rf .git + rm -rf .gitignore + rm -rf .github + rm -rf *.md + rm -rf *.sh + rm -rf *.psd + rm -rf *.go + - uses: peaceiris/actions-gh-pages@v2.5.0 + env: + ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} + PUBLISH_BRANCH: gh-pages + PUBLISH_DIR: ./_book + job_deploy_ebooks: + name: 'Deploy ebooks' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: '10.x' + - name: 'Installing gitbook cli' + run: npm install -g gitbook-cli + - name: 'Installing calibre' + run: sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin + - name: 'Preparing for ebooks generations' + run: | + cp book.json.template book.json + mkdir _book + gitbook install + - name: 'Generating ebook in pdf' + run: gitbook pdf ./ ./_book/dasarpemrogramangolang.pdf + - name: 'Generating ebook in epub' + run: gitbook epub ./ ./_book/dasarpemrogramangolang.epub + - name: 'Generating ebook in mobi' + run: gitbook mobi ./ ./_book/dasarpemrogramangolang.mobi + - uses: peaceiris/actions-gh-pages@v2.5.0 + env: + ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} + PUBLISH_BRANCH: ebooks + PUBLISH_DIR: ./_book diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..62733ed45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,91 @@ +# Node rules: +## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +## Dependency directory +## Commenting this out is preferred by some people, see +## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git +node_modules + +# Book build output +_book + +# eBook build output +*.epub +*.mobi +*.pdf + +# custom +web/# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless diff --git a/1-berkenalan-dengan-golang.md b/1-berkenalan-dengan-golang.md new file mode 100644 index 000000000..7f0cbb469 --- /dev/null +++ b/1-berkenalan-dengan-golang.md @@ -0,0 +1,24 @@ +# A.1. Berkenalan Dengan Golang + +**Golang** (atau biasa disebut dengan **Go**) adalah bahasa pemrograman baru yang dikembangkan di **Google** oleh **Robert Griesemer**, **Rob Pike**, dan **Ken Thompson** pada tahun 2007 dan mulai diperkenalkan di publik tahun 2009. + +Penciptaan bahasa Go didasari bahasa **C** dan **C++**, oleh karena itu gaya sintaks-nya mirip. + +## A.1.1. Kelebihan Go + +Go memiliki kelebihan dibanding bahasa lainnya, beberapa di antaranya: + +* Mendukung konkurensi di level bahasa dengan pengaplikasian cukup mudah +* Mendukung pemrosesan data dengan banyak prosesor dalam waktu yang bersamaan *(pararel processing)* +* Memiliki garbage collector +* Proses kompilasi sangat cepat +* Bukan bahasa pemrograman yang hirarkial, menjadikan developer tidak perlu *ribet* memikirkan segmen OOP-nya +* Package/modul yang disediakan terbilang lengkap. Karena bahasa ini open source, banyak sekali developer yang juga mengembangkan modul-modul lain yang bisa dimanfaatkan + +Sudah banyak industri dan perusahaan yg menggunakan Go sampai level production, termasuk diantaranya adalah Google sendiri, dan juga tempat dimana penulis bekerja 😁 + +--- + +Di buku ini (terutama semua serial bab A) kita akan belajar tentang dasar pemrograman Go, mulai dari 0. + +![The Go Logo](images/A.1_1_logo.png) diff --git a/10-konstanta.md b/10-konstanta.md new file mode 100644 index 000000000..ea8b09f19 --- /dev/null +++ b/10-konstanta.md @@ -0,0 +1,47 @@ +# A.10. Konstanta + +Konstanta adalah jenis variabel yang nilainya tidak bisa diubah. Inisialisasi nilai hanya dilakukan sekali di awal, setelah itu variabel tidak bisa diubah nilainya. + +## A.10.1. Penggunaan Konstanta + +Data seperti **pi** (22/7), kecepatan cahaya (299.792.458 m/s), adalah contoh data yang tepat jika dideklarasikan sebagai konstanta daripada variabel, karena nilainya sudah pasti dan tidak berubah. + +Cara penerapan konstanta sama seperti deklarasi variabel biasa, selebihnya tinggal ganti keyword `var` dengan `const`. + +```go +const firstName string = "john" +fmt.Print("halo ", firstName, "!\n") +``` + +Teknik type inference bisa diterapkan pada konstanta, caranya yaitu cukup dengan menghilangkan tipe data pada saat deklarasi. + +```go +const lastName = "wick" +fmt.Print("nice to meet you ", lastName, "!\n") +``` + +### A.10.1.1. Penggunaan Fungsi `fmt.Print()` + +Fungsi ini memiliki peran yang sama seperti fungsi `fmt.Println()`, pembedanya fungsi `fmt.Print()` tidak menghasilkan baris baru di akhir outputnya. + +Perbedaan lainnya adalah, nilai pada parameter-parameter yang dimasukkan ke fungsi tersebut digabungkan tanpa pemisah. Tidak seperti pada fungsi `fmt.Println()` yang nilai paremeternya digabung menggunakan penghubung spasi. + +```go +fmt.Println("john wick") +fmt.Println("john", "wick") + +fmt.Print("john wick\n") +fmt.Print("john ", "wick\n") +fmt.Print("john", " ", "wick\n") +``` + +Kode di atas menunjukkan perbedaan antara `fmt.Println()` dan `fmt.Print()`. Output yang dihasilkan oleh 5 statement di atas adalah sama, meski cara yang digunakan berbeda. + +Bila menggunakan `fmt.Println()` tidak perlu menambahkan spasi di tiap kata, karena fungsi tersebut akan secara otomatis menambahkannya di sela-sela nilai. Berbeda dengan `fmt.Print()`, perlu ditambahkan spasi, karena fungsi ini tidak menambahkan spasi di sela-sela nilai parameter yang digabungkan. + +--- + + diff --git a/11-operator.md b/11-operator.md new file mode 100644 index 000000000..782f2a32b --- /dev/null +++ b/11-operator.md @@ -0,0 +1,98 @@ +# A.11. Operator + +Bab ini membahas mengenai macam operator yang bisa digunakan di Go. Secara umum terdapat 3 kategori operator: aritmatika, perbandingan, dan logika. + +## A.11.1. Operator Aritmatika + +Operator aritmatika adalah operator yang digunakan untuk operasi yang sifatnya perhitungan. Go mendukung beberapa operator aritmatika standar, list-nya bisa dilihat di tabel berikut. + +| Tanda | Penjelasan | +| :---: | :--------- | +| `+` | penjumlahan | +| `-` | pengurangan | +| `*` | perkalian | +| `/` | pembagian | +| `%` | modulus / sisa hasil pembagian | + +Contoh penggunaan: + +```go +var value = (((2 + 6) % 3) * 4 - 2) / 3 +``` + +## A.11.2. Operator Perbandingan + +Operator perbandingan digunakan untuk menentukan kebenaran suatu kondisi. Hasilnya berupa nilai boolean, `true` atau `false`. + +Tabel di bawah ini berisikan operator perbandingan yang bisa digunakan di Go. + +| Tanda | Penjelasan | +| :---: | :--------- | +| `==` | apakah nilai kiri **sama dengan** nilai kanan | +| `!=` | apakah nilai kiri **tidak sama dengan** nilai kanan | +| `<` | apakah nilai kiri **lebih kecil daripada** nilai kanan | +| `<=` | apakah nilai kiri **lebih kecil atau sama dengan** nilai kanan | +| `>` | apakah nilai kiri **lebih besar dari** nilai kanan | +| `>=` | apakah nilai kiri **lebih besar atau sama dengan** nilai kanan | + +Contoh penggunaan: + +```go +var value = (((2 + 6) % 3) * 4 - 2) / 3 +var isEqual = (value == 2) + +fmt.Printf("nilai %d (%t) \n", value, isEqual) +``` + +Pada kode di atas, terdapat statement operasi aritmatika yang hasilnya ditampung oleh variabel `value`. Selanjutnya, variabel tersebut tersebut dibandingkan dengan angka **2** untuk dicek apakah nilainya sama. Jika iya, maka hasilnya adalah `true`, jika tidak maka `false`. Nilai hasil operasi perbandingan tersebut kemudian disimpan dalam variabel `isEqual`. + +![Penggunaan operator perbandingan](images/A.11_1_operator_comparison.png) + +Untuk memunculkan nilai `bool` menggunakan `fmt.Printf()`, bisa gunakan layout format `%t`. + +## A.11.3. Operator Logika + +Operator ini digunakan untuk mencari benar tidaknya kombinasi data bertipe `bool` (bisa berupa variabel bertipe `bool`, atau hasil dari operator perbandingan). + +Beberapa operator logika standar yang bisa digunakan: + +| Tanda | Penjelasan | +| :---: | :--------- | +| `&&` | kiri **dan** kanan | +| || | kiri **atau** kanan | +| `!` | negasi / nilai kebalikan | + +Contoh penggunaan: + +```go +var left = false +var right = true + +var leftAndRight = left && right +fmt.Printf("left && right \t(%t) \n", leftAndRight) + +var leftOrRight = left || right +fmt.Printf("left || right \t(%t) \n", leftOrRight) + +var leftReverse = !left +fmt.Printf("!left \t\t(%t) \n", leftReverse) +``` + +Hasil dari operator logika sama dengan hasil dari operator perbandingan, yaitu berupa boolean. + +![Penerapan operator logika](images/A.11_2_operator_logical.png) + +Berikut penjelasan statemen operator logika pada kode di atas. + + - `leftAndRight` bernilai `false`, karena hasil dari `false` **dan** `true` adalah `false`. + - `leftOrRight` bernilai `true`, karena hasil dari `false` **atau** `true` adalah `true`. + - `leftReverse` bernilai `true`, karena **negasi** (atau lawan dari) `false` adalah `true`. + +Template `\t` digunakan untuk menambahkan indent tabulasi. Biasa dimanfaatkan untuk merapikan tampilan output pada console. + +--- + + diff --git a/12-seleksi-kondisi.md b/12-seleksi-kondisi.md new file mode 100644 index 000000000..6eb88a749 --- /dev/null +++ b/12-seleksi-kondisi.md @@ -0,0 +1,208 @@ +# A.12. Seleksi Kondisi + +Seleksi kondisi digunakan untuk mengontrol alur program. Analoginya mirip seperti fungsi rambu lalu lintas di jalan raya. Kapan kendaraan diperbolehkan melaju dan kapan harus berhenti diatur oleh rambu tersebut. Seleksi kondisi pada program juga kurang lebih sama, kapan sebuah blok kode akan dieksekusi dikontrol. + +Yang dijadikan acuan oleh seleksi kondisi adalah nilai bertipe `bool`, bisa berasal dari variabel, ataupun hasil operasi perbandingan. Nilai tersebut menentukan blok kode mana yang akan dieksekusi. + +Go memiliki 2 macam keyword untuk seleksi kondisi, yaitu **if else** dan **switch**. Di bab ini kita akan mempelajarinya satu-persatu. + +> Go tidak mendukung seleksi kondisi menggunakan **ternary**.
Statement seperti: `var data = (isExist ? "ada" : "tidak ada")` adalah invalid dan menghasilkan error. + +## A.12.1. Seleksi Kondisi Menggunakan Keyword `if`, `else if`, & `else` + +Cara penerapan if-else di Go sama seperti pada bahasa pemrograman lain. Yang membedakan hanya tanda kurungnya *(parentheses)*, di Go tidak perlu ditulis. Kode berikut merupakan contoh penerapan seleksi kondisi if else, dengan jumlah kondisi 4 buah. + +```go +var point = 8 + +if point == 10 { + fmt.Println("lulus dengan nilai sempurna") +} else if point > 5 { + fmt.Println("lulus") +} else if point == 4 { + fmt.Println("hampir lulus") +} else { + fmt.Printf("tidak lulus. nilai anda %d\n", point) +} +``` + +Dari ke-empat kondisi di atas, yang terpenuhi adalah `if point > 5`, karena nilai variabel `point` memang lebih besar dari `5`. Maka blok kode tepat dibawah kondisi tersebut akan dieksekusi (blok kode ditandai kurung kurawal buka dan tutup), hasilnya text `"lulus"` muncul sebagai output. + +![Seleksi kondisi `if` - `else`](images/A.12_1_if_else.png) + +Skema if else Go sama seperti pada pemrograman umumnya. Yaitu di awal seleksi kondisi menggunakan `if`, dan ketika kondisinya tidak terpenuhi akan menuju ke `else` (jika ada). Ketika ada banyak kondisi, gunakan `else if`. + +> Di bahasa pemrograman lain, ketika ada seleksi kondisi yang isi blok-nya hanya 1 baris saja, kurung kurawal boleh tidak dituliskan. Berbeda dengan aturan di Go, kurung kurawal harus tetap dituliskan meski isinya hanya 1 blok satement. + +## A.12.2. Variabel Temporary Pada `if` - `else` + +Variabel temporary adalah variabel yang hanya bisa digunakan pada blok seleksi kondisi dimana ia ditempatkan saja. Penggunaan variabel ini membawa beberapa manfaat, antara lain: + + - Scope atau cakupan variabel jelas, hanya bisa digunakan pada blok seleksi kondisi itu saja + - Kode menjadi lebih rapi + - Ketika nilai variabel tersebut didapat dari sebuah komputasi, perhitungan tidak perlu dilakukan di dalam blok masing-masing kondisi. + +```go +var point = 8840.0 + +if percent := point / 100; percent >= 100 { + fmt.Printf("%.1f%s perfect!\n", percent, "%") +} else if percent >= 70 { + fmt.Printf("%.1f%s good\n", percent, "%") +} else { + fmt.Printf("%.1f%s not bad\n", percent, "%") +} +``` + +Variabel `percent` nilainya didapat dari hasil perhitungan, dan hanya bisa digunakan di deretan blok seleksi kondisi itu saja. + +> Deklarasi variabel temporary hanya bisa dilakukan lewat metode type inference yang menggunakan tanda `:=`. Penggunaan keyword `var` disitu tidak diperbolehkan karena akan menyebabkan error. + +## A.12.3. Seleksi Kondisi Menggunakan Keyword `switch` - `case` + +Switch merupakan seleksi kondisi yang sifatnya fokus pada satu variabel, lalu kemudian di-cek nilainya. Contoh sederhananya seperti penentuan apakah nilai variabel `x` adalah: `1`, `2`, `3`, atau lainnya. + +```go +var point = 6 + +switch point { +case 8: + fmt.Println("perfect") +case 7: + fmt.Println("awesome") +default: + fmt.Println("not bad") +} +``` + +Pada kode di atas, tidak ada kondisi atau `case` yang terpenuhi karena nilai variabel `point` tetap `6`. Ketika hal seperti ini terjadi, blok kondisi `default` dipanggil. Bisa dibilang bahwa `default` merupakan `else` dalam sebuah switch. + +Perlu diketahui, switch pada pemrograman Go memiliki perbedaan dibanding bahasa lain. Di Go, ketika sebuah case terpenuhi, tidak akan dilanjutkan ke pengecekkan case selanjutnya, meskipun tidak ada keyword `break` di situ. Konsep ini berkebalikan dengan switch pada umumnya, yang ketika sebuah case terpenuhi, maka akan tetap dilanjut mengecek case selanjutnya kecuali ada keyword `break`. + +## A.12.4. Pemanfaatan `case` Untuk Banyak Kondisi + +Sebuah `case` dapat menampung banyak kondisi. Cara penerapannya yaitu dengan menuliskan nilai pembanding-pembanding variabel yang di-switch setelah keyword `case` dipisah tanda koma (`,`). + +```go +var point = 6 + +switch point { +case 8: + fmt.Println("perfect") +case 7, 6, 5, 4: + fmt.Println("awesome") +default: + fmt.Println("not bad") +} +``` + +Kondisi `case 7, 6, 5, 4:` akan terpenuhi ketika nilai variabel `point` adalah 7 atau 6 atau 5 atau 4. + +## A.12.5. Kurung Kurawal Pada Keyword `case` & `default` + +Tanda kurung kurawal (`{ }`) bisa diterapkan pada keyword `case` dan `default`. Tanda ini opsional, boleh dipakai boleh tidak. Bagus jika dipakai pada blok kondisi yang didalamnya ada banyak statement, kode akan terlihat lebih rapi dan mudah di-maintain. + +Perhatikan kode berikut, bisa dilihat pada keyword `default` terdapat kurung kurawal yang mengapit 2 statement didalamnya. + +```go +var point = 6 + +switch point { +case 8: + fmt.Println("perfect") +case 7, 6, 5, 4: + fmt.Println("awesome") +default: + { + fmt.Println("not bad") + fmt.Println("you can be better!") + } +} +``` + +## A.12.6. Switch Dengan Gaya `if` - `else` + +Uniknya di Go, switch bisa digunakan dengan gaya ala if-else. Nilai yang akan dibandingkan tidak dituliskan setelah keyword `switch`, melainkan akan ditulis langsung dalam bentuk perbandingan dalam keyword `case`. + +Pada kode di bawah ini, kode program switch di atas diubah ke dalam gaya `if-else`. Variabel `point` dihilangkan dari keyword `switch`, lalu kondisi-kondisinya dituliskan di tiap `case`. + +```go +var point = 6 + +switch { +case point == 8: + fmt.Println("perfect") +case (point < 8) && (point > 3): + fmt.Println("awesome") +default: + { + fmt.Println("not bad") + fmt.Println("you need to learn more") + } +} +``` + +## A.12.7. Penggunaan Keyword `fallthrough` Dalam `switch` + +Seperti yang sudah dijelaskan sebelumnya, bahwa switch pada Go memiliki perbedaan dengan bahasa lain. Ketika sebuah `case` terpenuhi, pengecekkan kondisi tidak akan diteruskan ke case-case setelahnya. + +Keyword `fallthrough` digunakan untuk memaksa proses pengecekkan diteruskan ke `case` selanjutnya dengan **tanpa menghiraukan nilai kondisinya**, jadi case di pengecekan selanjutnya tersebut selalu dianggap benar (meskipun aslinya adalah salah). + +```go +var point = 6 + +switch { +case point == 8: + fmt.Println("perfect") +case (point < 8) && (point > 3): + fmt.Println("awesome") + fallthrough +case point < 5: + fmt.Println("you need to learn more") +default: + { + fmt.Println("not bad") + fmt.Println("you need to learn more") + } +} +``` + + +Setelah pengecekkan `case (point < 8) && (point > 3)` selesai, akan dilanjut ke pengecekkan `case point < 5`, karena ada `fallthrough` di situ. + +![Penggunaan `fallthrough` dalam `switch`](images/A.12_2_fallthrough.png) + +## A.12.8. Seleksi Kondisi Bersarang + +Seleksi kondisi bersarang adalah seleksi kondisi, yang berada dalam seleksi kondisi, yang mungkin juga berada dalam seleksi kondisi, dan seterusnya. Seleksi kondisi bersarang bisa dilakukan pada `if` - `else`, `switch`, ataupun kombinasi keduanya. + +```go +var point = 10 + +if point > 7 { + switch point { + case 10: + fmt.Println("perfect!") + default: + fmt.Println("nice!") + } +} else { + if point == 5 { + fmt.Println("not bad") + } else if point == 3 { + fmt.Println("keep trying") + } else { + fmt.Println("you can do it") + if point == 0 { + fmt.Println("try harder!") + } + } +} +``` + +--- + + diff --git a/13-perulangan.md b/13-perulangan.md new file mode 100644 index 000000000..d08aff6c8 --- /dev/null +++ b/13-perulangan.md @@ -0,0 +1,135 @@ +# A.13. Perulangan + +Perulangan adalah proses mengulang-ulang eksekusi blok kode tanpa henti, selama kondisi yang dijadikan acuan terpenuhi. Biasanya disiapkan variabel untuk iterasi atau variabel penanda kapan perulangan akan diberhentikan. + +Di Go keyword perulangan hanya **for** saja, tetapi meski demikian, kemampuannya merupakan gabungan `for`, `foreach`, dan `while` ibarat bahasa pemrograman lain. + +## A.13.1. Perulangan Menggunakan Keyword `for` + +Ada beberapa cara standar menggunakan `for`. Cara pertama dengan memasukkan variabel counter perulangan beserta kondisinya setelah keyword. Perhatikan dan praktekan kode berikut. + +```go +for i := 0; i < 5; i++ { + fmt.Println("Angka", i) +} +``` + +Perulangan di atas hanya akan berjalan ketika variabel `i` bernilai dibawah `5`, dengan ketentuan setiap kali perulangan, nilai variabel `i` akan di-iterasi atau ditambahkan 1 (`i++` artinya ditambah satu, sama seperti `i = i + 1`). Karena `i` pada awalnya bernilai 0, maka perulangan akan berlangsung 5 kali, yaitu ketika `i` bernilai 0, 1, 2, 3, dan 4. + +![Penggunaan `for`](images/A.13_1_for.png) + +## A.13.2. Penggunaan Keyword `for` Dengan Argumen Hanya Kondisi + +Cara ke-2 adalah dengan menuliskan kondisi setelah keyword `for` (hanya kondisi). Deklarasi dan iterasi variabel counter tidak dituliskan setelah keyword, hanya kondisi perulangan saja. Konsepnya mirip seperti `while` milik bahasa pemrograman lain. + +Kode berikut adalah contoh `for` dengan argumen hanya kondisi (seperti `if`), output yang dihasilkan sama seperti penerapan for cara pertama. + +```go +var i = 0 + +for i < 5 { + fmt.Println("Angka", i) + i++ +} +``` + +## A.13.3. Penggunaan Keyword `for` Tanpa Argumen + +Cara ke-3 adalah `for` ditulis tanpa kondisi. Dengan ini akan dihasilkan perulangan tanpa henti (sama dengan `for true`). Pemberhentian perulangan dilakukan dengan menggunakan keyword `break`. + +```go +var i = 0 + +for { + fmt.Println("Angka", i) + + i++ + if i == 5 { + break + } +} +``` + +Dalam perulangan tanpa henti di atas, variabel `i` yang nilai awalnya `0` di-inkrementasi. Ketika nilai `i` sudah mencapai `5`, keyword `break` digunakan, dan perulangan akan berhenti. + +## A.13.4. Penggunaan Keyword `for` - `range` + +Cara ke-4 adalah perulangan dengan menggunakan kombinasi keyword `for` dan `range`. Cara ini biasa digunakan untuk me-looping data bertipe array. Detailnya akan dibahas dalam bab selanjutnya (bab 14). + +## A.13.5. Penggunaan Keyword `break` & `continue` + +Keyword `break` digunakan untuk menghentikan secara paksa sebuah perulangan, sedangkan `continue` dipakai untuk memaksa maju ke perulangan berikutnya. + +Berikut merupakan contoh penerapan `continue` dan `break`. Kedua keyword tersebut dimanfaatkan untuk menampilkan angka genap berurutan yang lebih besar dari 0 dan dibawah 8. + +```go +for i := 1; i <= 10; i++ { + if i % 2 == 1 { + continue + } + + if i > 8 { + break + } + + fmt.Println("Angka", i) +} +``` + +Kode di atas akan lebih mudah dicerna jika dijelaskan secara berurutan. Berikut adalah penjelasannya. + + 1. Dilakukan perulangan mulai angka 1 hingga 10 dengan `i` sebagai variabel iterasi. + 2. Ketika `i` adalah ganjil (dapat diketahui dari `i % 2`, jika hasilnya `1`, berarti ganjil), maka akan dipaksa lanjut ke perulangan berikutnya. + 3. Ketika `i` lebih besar dari 8, maka perulangan akan berhenti. + 4. Nilai `m` ditampilkan. + +![Penerapan keyword `for`, `break`, dan `continue`](images/A.13_2_for_break_continue.png) + +## A.13.6. Perulangan Bersarang + +Tak hanya seleksi kondisi yang bisa bersarang, perulangan juga bisa. Cara pengaplikasiannya kurang lebih sama, tinggal tulis blok statement perulangan didalam perulangan. + +```go +for i := 0; i < 5; i++ { + for j := i; j < 5; j++ { + fmt.Print(j, " ") + } + + fmt.Println() +} +``` + +Pada kode di atas, untuk pertama kalinya fungsi `fmt.Println()` dipanggil tanpa disisipkan parameter. Cara seperti ini bisa digunakan untuk menampilkan baris baru. Kegunaannya sama seperti output dari statement `fmt.Print("\n")`. + +![Perulangan bersarang](images/A.13_3_nested_for.png) + +## A.13.7. Pemanfaatan Label Dalam Perulangan + +Di perulangan bersarang, `break` dan `continue` akan berlaku pada blok perulangan dimana ia digunakan saja. Ada cara agar kedua keyword ini bisa tertuju pada perulangan terluar atau perulangan tertentu, yaitu dengan memanfaatkan teknik pemberian **label**. + +Program untuk memunculkan matriks berikut merupakan contoh penerapan label perulangan. + +```go +outerLoop: +for i := 0; i < 5; i++ { + for j := 0; j < 5; j++ { + if i == 3 { + break outerLoop + } + fmt.Print("matriks [", i, "][", j, "]", "\n") + } +} +``` + +Tepat sebelum keyword `for` terluar, terdapat baris kode `outerLoop:`. Maksud dari kode tersebut adalah disiapkan sebuah label bernama `outerLoop` untuk `for` dibawahnya. Nama label bisa diganti dengan nama lain (dan harus diakhiri dengan tanda titik dua atau *colon* (`:`) ). + +Pada `for` bagian dalam, terdapat seleksi kondisi untuk pengecekan nilai `i`. Ketika nilai tersebut sama dengan `3`, maka `break` dipanggil dengan target adalah perulangan yang dilabeli `outerLoop`, perulangan tersebut akan dihentikan. + +![Penerapan label dalam perulangan](images/A.13_4_for_label.png) + +--- + + diff --git a/14-array.md b/14-array.md new file mode 100644 index 000000000..1281bf66c --- /dev/null +++ b/14-array.md @@ -0,0 +1,202 @@ +# A.14. Array + +Array adalah kumpulan data bertipe sama, yang disimpan dalam sebuah variabel. Array memiliki kapasitas yang nilainya ditentukan pada saat pembuatan, menjadikan elemen/data yang disimpan di array tersebut jumlahnya tidak boleh melebihi yang sudah dialokasikan. Default nilai tiap elemen array pada awalnya tergantung dari tipe datanya. Jika `int` maka tiap element zero value-nya adalah `0`, jika `bool` maka `false`, dan seterusnya. Setiap elemen array memiliki indeks berupa angka yang merepresentasikan posisi urutan elemen tersebut. Indeks array dimulai dari 0. + +Contoh penerapan array: + +```go +var names [4]string +names[0] = "trafalgar" +names[1] = "d" +names[2] = "water" +names[3] = "law" + +fmt.Println(names[0], names[1], names[2], names[3]) +``` + +Variabel `names` dideklarasikan sebagai `array string` dengan alokasi elemen `4` slot. Cara mengisi slot elemen array bisa dilihat di kode di atas, yaitu dengan langsung mengakses elemen menggunakan indeks, lalu mengisinya. + +![Menampilkan elemen array](images/A.14_0_array.png) + +## A.14.1. Pengisian Elemen Array yang Melebihi Alokasi Awal + +Pengisian elemen array pada indeks yang tidak sesuai dengan alokasi menghasilkan error. Contoh sederhana, jika array memiliki 4 slot, maka pengisian nilai slot 5 seterusnya adalah tidak valid. + +```go +var names [4]string +names[0] = "trafalgar" +names[1] = "d" +names[2] = "water" +names[3] = "law" +names[4] = "ez" // baris kode ini menghasilkan error +``` + +Solusi dari masalah di atas adalah dengan menggunakan keyword `append`, yang di bab selanjutnya ([Bab A.15. Slice](/15-slice.html)) akan kita bahas. + +## A.14.2. Inisialisasi Nilai Awal Array + +Pengisian elemen array bisa dilakukan pada saat deklarasi variabel. Caranya dengan menuliskan data elemen dalam kurung kurawal setelah tipe data, dengan pembatas antar elemen adalah tanda koma (`,`). + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +fmt.Println("Jumlah element \t\t", len(fruits)) +fmt.Println("Isi semua element \t", fruits) +``` + +Penggunaan fungsi `fmt.Println()` pada data array tanpa mengakses indeks tertentu, akan menghasilkan output dalam bentuk string dari semua array yang ada. Teknik ini biasa digunakan untuk **debugging** data array. + +![Menghitung jumlah elemen dan menampilkan isi array](images/A.14_1_array_initialization_and_len.png) + +Fungsi `len()` dipakai untuk menghitung jumlah elemen sebuah array. + +## A.14.3. Inisialisasi Nilai Array Dengan Gaya Vertikal + +Elemen array bisa dituliskan dalam bentuk horizontal (seperti yang sudah dicontohkan di atas) ataupun dalam bentuk vertikal. + +```go +var fruits [4]string + +// cara horizontal +fruits = [4]string{"apple", "grape", "banana", "melon"} + +// cara vertikal +fruits = [4]string{ + "apple", + "grape", + "banana", + "melon", +} +``` + +Khusus untuk deklarasi array dengan cara vertikal, tanda koma wajib dituliskan setelah elemen, termasuk elemen terakhir. Jika tidak, maka akan muncul error. + +## A.14.4. Inisialisasi Nilai Awal Array Tanpa Jumlah Elemen + +Deklarasi array yang nilainya diset di awal, boleh tidak dituliskan jumlah lebar array-nya, cukup ganti dengan tanda 3 titik (`...`). Jumlah elemen akan dikalkulasi secara otomatis menyesuaikan data elemen yang diisikan. + +```go +var numbers = [...]int{2, 3, 2, 4, 3} + +fmt.Println("data array \t:", numbers) +fmt.Println("jumlah elemen \t:", len(numbers)) +``` + +Variabel `numbers` akan secara otomatis memiliki jumlah elemen `5`, karena pada saat deklarasi disiapkan 5 buah elemen. + +![Deklarasi array menggunakan tanda 3 titik](images/A.14_1_1_array_dots.png) + +## A.14.5. Array Multidimensi + +Array multidimensi adalah array yang tiap elemennya juga berupa array (dan bisa seterusnya, tergantung kedalaman dimensinya). + +Cara deklarasi array multidimensi secara umum sama dengan cara deklarasi array biasa, dengan cara menuliskan data array dimensi selanjutnya sebagai elemen array dimensi sebelumnya. + +Khusus untuk array yang merupakan sub dimensi atau elemen, boleh tidak dituliskan jumlah datanya. Contohnya bisa dilihat pada deklarasi variabel `numbers2` di kode berikut. + +```go +var numbers1 = [2][3]int{[3]int{3, 2, 3}, [3]int{3, 4, 5}} +var numbers2 = [2][3]int{{3, 2, 3}, {3, 4, 5}} + +fmt.Println("numbers1", numbers1) +fmt.Println("numbers2", numbers2) +``` + +Kedua array di atas memiliki elemen yang sama. + +![Array multidimensi](images/A.14_2_array_multidimension.png) + +## A.14.6. Perulangan Elemen Array Menggunakan Keyword `for` + +Keyword `for` dan array memiliki hubungan yang sangat erat. Dengan memanfaatkan perulangan menggunakan keyword ini, elemen-elemen dalam array bisa didapat. + +Ada beberapa cara yang bisa digunakan untuk me-looping data array, yg pertama adalah dengan memanfaatkan variabel iterasi perulangan untuk mengakses elemen berdasarkan indeks-nya. Contoh: + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +for i := 0; i < len(fruits); i++ { + fmt.Printf("elemen %d : %s\n", i, fruits[i]) +} +``` + +Perulangan di atas dijalankan sebanyak jumlah elemen array `fruits` (bisa diketahui dari kondisi `i < len(fruits`). Di tiap perulangan, elemen array diakses lewat variabel iterasi `i`. + +![Iterasi elemen array](images/A.14_3_for_range.png) + +## A.14.7. Perulangan Elemen Array Menggunakan Keyword `for` - `range` + +Ada cara yang lebih sederhana me-looping data array, dengan menggunakan keyword `for` - `range`. Contoh pengaplikasiannya bisa dilihat di kode berikut. + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +for i, fruit := range fruits { + fmt.Printf("elemen %d : %s\n", i, fruit) +} +``` + +Array `fruits` diambil elemen-nya secara berurutan. Nilai tiap elemen ditampung variabel oleh `fruit` (tanpa huruf s), sedangkan indeks nya ditampung variabel `i`. + +Output program di atas, sama dengan output program sebelumnya, hanya cara yang digunakan berbeda. + +## A.14.8. Penggunaan Variabel Underscore `_` Dalam `for` - `range` + +Kadang kala ketika *looping* menggunakan `for` - `range`, ada kemungkinan dimana data yang dibutuhkan adalah elemen-nya saja, indeks-nya tidak. Sedangkan kode di atas, `range` mengembalikan 2 data, yaitu indeks dan elemen. + +Seperti yang sudah diketahui, bahwa di Go tidak memperbolehkan adanya variabel yang menaggur atau tidak dipakai. Jika dipaksakan, error akan muncul, contohnya seperti kode berikut. + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +for i, fruit := range fruits { + fmt.Printf("nama buah : %s\n", fruit) +} +``` + +Hasil dari kode program di atas: + +![Error karena ada variabel yang tidak digunakan](images/A.14_4_for_range_error.png) + +Disinilah salah satu kegunaan variabel pengangguran, atau underscore (`_`). Tampung saja nilai yang tidak ingin digunakan ke underscore. + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +for _, fruit := range fruits { + fmt.Printf("nama buah : %s\n", fruit) +} +``` + +Pada kode di atas, yang sebelumnya adalah variabel `i` diganti dengan `_`, karena kebetulan variabel `i` tidak digunakan. + +![For range tanpa indeks](images/A.14_5_for_range_underscore.png) + +Jika yang dibutuhkan hanya indeks elemen-nya saja, bisa gunakan 1 buah variabel setelah keyword `for`. + +```go +for i, _ := range fruits { } +// atau +for i := range fruits { } +``` + +## A.14.9. Alokasi Elemen Array Menggunakan Keyword `make` + +Deklarasi sekaligus alokasi data array juga bisa dilakukan lewat keyword `make`. + +```go +var fruits = make([]string, 2) +fruits[0] = "apple" +fruits[1] = "manggo" + +fmt.Println(fruits) // [apple manggo] +``` + +Parameter pertama keyword `make` diisi dengan tipe data elemen array yang diinginkan, parameter kedua adalah jumlah elemennya. Pada kode di atas, variabel `fruits` tercetak sebagai array string dengan alokasi 2 slot. + +--- + + diff --git a/15-slice.md b/15-slice.md new file mode 100644 index 000000000..5390fec13 --- /dev/null +++ b/15-slice.md @@ -0,0 +1,263 @@ +# A.15. Slice + +**Slice** adalah *reference* elemen array. Slice bisa dibuat, atau bisa juga dihasilkan dari manipulasi sebuah array ataupun slice lainnya. Karena merupakan data *reference*, menjadikan perubahan data di tiap elemen slice akan berdampak pada slice lain yang memiliki alamat memori yang sama. + +## A.15.1. Inisialisasi Slice + +Cara pembuatan slice mirip seperti pembuatan array, bedanya tidak perlu mendefinisikan jumlah elemen ketika awal deklarasi. Pengaksesan nilai elemen-nya juga sama. Contoh pembuatan slice: + +```go +var fruits = []string{"apple", "grape", "banana", "melon"} +fmt.Println(fruits[0]) // "apple" +``` + +Salah satu perbedaan slice dan array bisa diketahui pada saat deklarasi variabel-nya, jika jumlah elemen tidak dituliskan, maka variabel tersebut adalah slice. + +```go +var fruitsA = []string{"apple", "grape"} // slice +var fruitsB = [2]string{"banana", "melon"} // array +var fruitsC = [...]string{"papaya", "grape"} // array +``` + +## A.15.2. Hubungan Slice Dengan Array & Operasi Slice + +Kalau perbedannya hanya di penentuan alokasi pada saat inisialisasi, kenapa tidak menggunakan satu istilah saja? atau adakah perbedaan lainnya? + +Sebenarnya slice dan array tidak bisa dibedakan karena merupakan sebuah kesatuan. Array adalah kumpulan nilai atau elemen, sedang slice adalah referensi tiap elemen tersebut. + +Slice bisa dibentuk dari array yang sudah didefinisikan, caranya dengan memanfaatkan teknik **2 index** untuk mengambil elemen-nya. Contoh bisa dilihat pada kode berikut. + +```go +var fruits = []string{"apple", "grape", "banana", "melon"} +var newFruits = fruits[0:2] + +fmt.Println(newFruits) // ["apple", "grape"] +``` + +Kode `fruits[0:2]` maksudnya adalah pengaksesan elemen dalam slice `fruits` yang **dimulai dari indeks ke-0, hingga elemen sebelum indeks ke-2**. Elemen yang memenuhi kriteria tersebut akan didapat, untuk kemudian disimpan pada variabel lain sebagai slice baru. Pada contoh di atas, `newFruits` adalah slice baru yang tercetak dari slice `fruits`, dengan isi 2 elemen, yaitu `"apple"` dan `"grape"`. + +![Memanfaatkan sebuah slice untuk membentuk slice baru](images/A.15_1_array_index.png) + +Ketika mengakses elemen array menggunakan satu buah indeks (seperti `data[2]`), nilai yang didapat merupakan hasil **copy** dari referensi aslinya. Berbeda dengan pengaksesan elemen menggunakan 2 indeks (seperti `data[0:2]`), nilai yang didapat adalah *reference* elemen atau slice. + +> Tidak apa jikalau pembaca masih bingung, di bawah akan dijelaskan lebih mendetail lagi tentang slice dan *reference* + +Tabel berikut adalah list operasi operasi menggunakan teknik 2 indeks yang bisa dilakukan. + +```go +var fruits = []string{"apple", "grape", "banana", "melon"} +``` + +| Kode | Output | Penjelasan | +| :--------- | :--------------------- | :---------- | +| `fruits[0:2]` | `[apple, grape]` | semua elemen mulai indeks ke-0, hingga sebelum indeks ke-2 | +| `fruits[0:4]` | [apple, grape, banana, melon] | semua elemen mulai indeks ke-0, hingga sebelum indeks ke-4 | +| `fruits[0:0]` | `[]` | menghasilkan slice kosong, karena tidak ada elemen sebelum indeks ke-0 | +| `fruits[4:4]` | `[]` | menghasilkan slice kosong, karena tidak ada elemen yang dimulai dari indeks ke-4 | +| `fruits[4:0]` | `[]` | error, pada penulisan `fruits[a,b]` nilai `a` harus lebih besar atau sama dengan `b` | +| `fruits[:]` | `[apple, grape, banana, melon]` | semua elemen | +| `fruits[2:]` | `[banana, melon]` | semua elemen mulai indeks ke-2 | +| `fruits[:2]` | `[apple, grape]` | semua elemen hingga sebelum indeks ke-2 | + +## A.15.3. Slice Merupakan Tipe Data Reference + +Slice merupakan tipe data *reference* atau referensi. Artinya jika ada slice baru yang terbentuk dari slice lama, maka data elemen slice yang baru akan memiliki alamat memori yang sama dengan elemen slice lama. Setiap perubahan yang terjadi di elemen slice baru, akan berdampak juga pada elemen slice lama yang memiliki referensi yang sama. + +Program berikut merupakan pembuktian tentang teori yang baru kita bahas. Kita akan mencoba mengubah data elemen slice baru, yang terbentuk dari slice lama. + +```go +var fruits = []string{"apple", "grape", "banana", "melon"} + +var aFruits = fruits[0:3] +var bFruits = fruits[1:4] + +var aaFruits = aFruits[1:2] +var baFruits = bFruits[0:1] + +fmt.Println(fruits) // [apple grape banana melon] +fmt.Println(aFruits) // [apple grape banana] +fmt.Println(bFruits) // [grape banana melon] +fmt.Println(aaFruits) // [grape] +fmt.Println(baFruits) // [grape] + +// Buah "grape" diubah menjadi "pinnaple" +baFruits[0] = "pinnaple" + +fmt.Println(fruits) // [apple pinnaple banana melon] +fmt.Println(aFruits) // [apple pinnaple banana] +fmt.Println(bFruits) // [pinnaple banana melon] +fmt.Println(aaFruits) // [pinnaple] +fmt.Println(baFruits) // [pinnaple] +``` + +Sekilas bisa kita lihat bahwa setelah slice yang isi datanya adalah `grape` di-ubah menjadi `pinnaple`, semua slice pada 4 variabel lainnya juga ikut berubah. + +Variabel `aFruits`, `bFruits` merupakan slice baru yang terbentuk dari variabel `fruits`. Dengan menggunakan dua slice baru tersebut, diciptakan lagi slice lainnya, yaitu `aaFruits`, dan `baFruits`. Kelima slice tersebut ditampilkan nilainya. + +Selanjutnya, nilai dari `baFruits[0]` diubah, dan 5 slice tadi ditampilkan lagi. Hasilnya akan ada banyak slice yang elemennya ikut berubah. Yaitu elemen-elemen yang referensi-nya sama dengan referensi elemen `baFruits[0]`. + +![Perubahan data elemen slice berpengaruh pada slice lain](images/A.15_2_slice_reference.png) + +Bisa dilihat pada output di atas, elemen yang sebelumnya bernilai `"grape"` pada variabel `fruits`, `aFruits`, `bFruits`, `aaFruits`, dan `baFruits`; kesemuanya berubah menjadi `"pinnaple"`, karena memiliki referensi yang sama. + +--- + +Pembahasan mengenai dasar slice sepertinya sudah cukup, selanjutnya kita akan membahas tentang beberapa *built in function* bawaan Go, yang bisa dimanfaatkan untuk keperluan operasi slice. + +## A.15.4. Fungsi `len()` + +Fungsi `len()` digunakan untuk menghitung jumlah elemen slice yang ada. Sebagai contoh jika sebuah variabel adalah slice dengan data 4 buah, maka fungsi ini pada variabel tersebut akan mengembalikan angka **4**. + +```go +var fruits = []string{"apple", "grape", "banana", "melon"} +fmt.Println(len(fruits)) // 4 +``` + +## A.15.5. Fungsi `cap()` + +Fungsi `cap()` digunakan untuk menghitung lebar atau kapasitas maksimum slice. Nilai kembalian fungsi ini untuk slice yang baru dibuat pasti sama dengan `len`, tapi bisa berubah seiring operasi slice yang dilakukan. Agar lebih jelas, silakan disimak kode berikut. + +```go +var fruits = []string{"apple", "grape", "banana", "melon"} +fmt.Println(len(fruits)) // len: 4 +fmt.Println(cap(fruits)) // cap: 4 + +var aFruits = fruits[0:3] +fmt.Println(len(aFruits)) // len: 3 +fmt.Println(cap(aFruits)) // cap: 4 + +var bFruits = fruits[1:4] +fmt.Println(len(bFruits)) // len: 3 +fmt.Println(cap(bFruits)) // cap: 3 +``` + +Variabel `fruits` disiapkan di awal dengan jumlah elemen 4, fungsi `len(fruits)` dan `cap(fruits)` pasti hasinya 4. + +Variabel `aFruits` dan `bFruits` merupakan slice baru berisikan 3 buah elemen milik slice `fruits`. Variabel `aFruits` mengambil elemen index 0, 1, 2; sedangkan `bFruits` 1, 2, 3. + +Fungsi `len()` menghasilkan angka 3, karena jumlah elemen kedua slice ini adalah 3. Tetapi `cap(aFruits)` menghasilkan angka yang berbeda, yaitu 4 untuk `aFruits` dan 3 untuk `bFruits`. kenapa? jawabannya bisa dilihat pada tabel berikut. + +| Kode | Output | `len()` | `cap()` | +| :--- | :----- | :-----: | :-----: | +| `fruits[0:4]` | [**`buah` `buah` `buah` `buah`**] | 4 | 4 | +| `aFruits[0:3]` | [**`buah` `buah` `buah`** `----`] | 3 | 4 | +| `bFruits[1:3]` | `----` [**`buah` `buah` `buah`**] | 3 | 3 | + +Kita analogikan slicing 2 index menggunakan **x** dan **y**. + +```go +fruits[x:y] +``` + +**Slicing** yang dimulai dari indeks **0** hingga **y** akan mengembalikan elemen-elemen mulai indeks **0** hingga sebelum indeks **y**, dengan lebar kapasitas adalah sama dengan slice aslinya. + +Sedangkan slicing yang dimulai dari indeks **x**, yang dimana nilai **x** adalah lebih dari **0**, membuat elemen ke-**x** slice yang diambil menjadi elemen ke-0 slice baru. Hal inilah yang membuat kapasitas slice berubah. + +## A.15.6. Fungsi `append()` + +Fungsi `append()` digunakan untuk menambahkan elemen pada slice. Elemen baru tersebut diposisikan setelah indeks paling akhir. Nilai balik fungsi ini adalah slice yang sudah ditambahkan nilai barunya. Contoh penggunaannya bisa dilihat di kode berikut. + +```go +var fruits = []string{"apple", "grape", "banana"} +var cFruits = append(fruits, "papaya") + +fmt.Println(fruits) // ["apple", "grape", "banana"] +fmt.Println(cFruits) // ["apple", "grape", "banana", "papaya"] +``` + +Ada 3 hal yang perlu diketahui dalam penggunaan fungsi ini. + + - Ketika jumlah elemen dan lebar kapasitas adalah sama (`len(fruits) == cap(fruits)`), maka elemen baru hasil `append()` merupakan referensi baru. + - Ketika jumlah elemen lebih kecil dibanding kapasitas (`len(fruits) < cap(fruits)`), elemen baru tersebut ditempatkan kedalam cakupan kapasitas, menjadikan semua elemen slice lain yang referensi-nya sama akan berubah nilainya. + +Agar lebih jelas silakan perhatikan contoh berikut. + +```go +var fruits = []string{"apple", "grape", "banana"} +var bFruits = fruits[0:2] + +fmt.Println(cap(bFruits)) // 3 +fmt.Println(len(bFruits)) // 2 + +fmt.Println(fruits) // ["apple", "grape", "banana"] +fmt.Println(bFruits) // ["apple", "grape"] + +var cFruits = append(bFruits, "papaya") + +fmt.Println(fruits) // ["apple", "grape", "papaya"] +fmt.Println(bFruits) // ["apple", "grape"] +fmt.Println(cFruits) // ["apple", "grape", "papaya"] +``` + +Pada contoh di atas bisa dilihat, elemen indeks ke-2 slice `fruits` nilainya berubah setelah ada penggunaan keyword `append()` pada `bFruits`. Slice `bFruits` kapasitasnya adalah **3** sedang jumlah datanya hanya **2**. Karena `len(bFruits) < cap(bFruits)`, maka elemen baru yang dihasilkan, terdeteksi sebagai perubahan nilai pada referensi yang lama (referensi elemen indeks ke-2 slice `fruits`), membuat elemen yang referensinya sama, nilainya berubah. + +## A.15.7. Fungsi `copy()` + +Fungsi `copy()` digunakan untuk men-copy elements slice pada `src` (parameter ke-2), ke `dst` (parameter pertama). + +```go +copy(dst, src) +``` + +Jumlah element yang di-copy dari `src` adalah sejumlah lebar slice `dst` (atau `len(dst)`). Jika jumlah slice pada `src` lebih kecil dari `dst`, maka akan ter-copy semua. Lebih jelasnya silakan perhatikan contoh berikut. + +```go +dst := make([]string, 3) +src := []string{"watermelon", "pinnaple", "apple", "orange"} +n := copy(dst, src) + +fmt.Println(dst) // watermelon pinnaple apple +fmt.Println(src) // watermelon pinnaple apple orange +fmt.Println(n) // 3 +``` + +Pada kode di atas variabel slice `dst` dipersiapkan dengan lebar adalah 3 elements. Slice `src` yang isinya 4 elements, di-copy ke `dst`. Menjadikan isi slice `dst` sekarang adalah 3 buah elements yang sama dengan 3 buah elements `src`, hasil dari operasi `copy()`. + +Yang ter-copy hanya 3 buah (meski `src` memiliki 4 elements) hal ini karena `copy()` hanya meng-copy elements sebanyak `len(dst)`. + +> Fungsi `copy()` mengembalikan informasi angka, representasi dari jumlah element yang berhasil di-copy. + +Pada contoh kedua berikut, `dst` merupakan slice yang sudah ada isinya, 3 buah elements. Variabel `src` yang juga merupakan slice dengan isi dua elements, di-copy ke `dst`. Karena operasi `copy()` akan meng-copy sejumlah `len(dst)`, maka semua elements `src` akan ter-copy **karena jumlahnya dibawah atau sama dengan lebar** `dst`. + +```go +dst := []string{"potato", "potato", "potato"} +src := []string{"watermelon", "pinnaple"} +n := copy(dst, src) + +fmt.Println(dst) // watermelon pinnaple potato +fmt.Println(src) // watermelon pinnaple +fmt.Println(n) // 2 +``` + +Jika dilihat pada kode di atas, isi `dst` masih tetap 3 elements, tapi dua elements pertama adalah sama dengan `src`. Element terakhir `dst` isinya tidak berubah, tetap `potato`, hal ini karena proses copy hanya memutasi element ke-1 dan ke-2 milik `dst`, karena memang pada `src` hanya dua itu elements-nya. + +## A.15.8. Pengaksesan Elemen Slice Dengan 3 Indeks + +**3 index** adalah teknik slicing elemen yang sekaligus menentukan kapasitasnya. Cara menggunakannnya yaitu dengan menyisipkan angka kapasitas di belakang, seperti `fruits[0:1:1]`. Angka kapasitas yang diisikan tidak boleh melebihi kapasitas slice yang akan di slicing. + +Berikut merupakan contoh penerapannya. + +```go +var fruits = []string{"apple", "grape", "banana"} +var aFruits = fruits[0:2] +var bFruits = fruits[0:2:2] + +fmt.Println(fruits) // ["apple", "grape", "banana"] +fmt.Println(len(fruits)) // len: 3 +fmt.Println(cap(fruits)) // cap: 3 + +fmt.Println(aFruits) // ["apple", "grape"] +fmt.Println(len(aFruits)) // len: 2 +fmt.Println(cap(aFruits)) // cap: 3 + +fmt.Println(bFruits) // ["apple", "grape"] +fmt.Println(len(bFruits)) // len: 2 +fmt.Println(cap(bFruits)) // cap: 2 +``` + +--- + + diff --git a/16-map.md b/16-map.md new file mode 100644 index 000000000..01f69c0e9 --- /dev/null +++ b/16-map.md @@ -0,0 +1,178 @@ +# A.16. Map + +**Map** adalah tipe data asosiatif yang ada di Go, berbentuk *key-value pair*. Untuk setiap data (atau value) yang disimpan, disiapkan juga key-nya. Key harus unik, karena digunakan sebagai penanda (atau identifier) untuk pengaksesan value yang bersangkutan. + +Kalau dilihat, `map` mirip seperti slice, hanya saja indeks yang digunakan untuk pengaksesan bisa ditentukan sendiri tipe-nya (indeks tersebut adalah key). + +## A.16.1. Penggunaan Map + +Cara menggunakan map cukup dengan menuliskan keyword `map` diikuti tipe data key dan value-nya. Agar lebih mudah dipahami, silakan perhatikan contoh di bawah ini. + +```go +var chicken map[string]int +chicken = map[string]int{} + +chicken["januari"] = 50 +chicken["februari"] = 40 + +fmt.Println("januari", chicken["januari"]) // januari 50 +fmt.Println("mei", chicken["mei"]) // mei 0 +``` + +Variabel `chicken` dideklarasikan sebagai map, dengan tipe data key adalah `string` dan value-nya `int`. Dari kode tersebut bisa dilihat bagaimana cara penggunaan keyword `map`. + +Kode `map[string]int` maknanya adalah, tipe data `map` dengan key bertipe `string` dan value bertipe `int`. + +Default nilai variabel `map` adalah `nil`. Oleh karena itu perlu dilakukan inisialisasi nilai default di awal, caranya cukup dengan tambahkan kurung kurawal pada akhir tipe, contoh seperti pada kode di atas: `map[string]int{}`. + +Cara menge-set nilai pada sebuah map adalah dengan menuliskan variabel-nya, kemudian disisipkan `key` pada kurung siku variabel (mirip seperti cara pengaksesan elemen slice), lalu isi nilainya. Contohnya seperti `chicken["februari"] = 40`. Sedangkan cara pengambilan value adalah cukup dengan menyisipkan `key` pada kurung siku variabel. + +Pengisian data pada map bersifat **overwrite**, ketika variabel sudah memiliki item dengan key yang sama, maka value lama akan ditimpa dengan value baru. + +![Pengaksesan data map](images/A.16_1_map_set_get.png) + +Pada pengaksesan item menggunakan key yang belum tersimpan di map, akan dikembalikan nilai default tipe data value-nya. Contohnya seperti pada kode di atas, `chicken["mei"]` menghasilkan nilai 0 (nilai default tipe `int`), karena belum ada item yang tersimpan menggunakan key `"mei"`. + +## A.16.2. Inisialisasi Nilai Map + +Zero value dari map adalah `nil`, maka tiap variabel bertipe map harus di-inisialisasi secara explisit nilai awalnya (agar tidak `nil`). + +```go +var data map[string]int +data["one"] = 1 +// akan muncul error! + +data = map[string]int{} +data["one"] = 1 +// tidak ada error +``` + +Nilai variabel bertipe map bisa didefinisikan di awal, caranya dengan menambahkan kurung kurawal setelah tipe data, lalu menuliskan key dan value didalamnya. Cara ini sekilas mirip dengan definisi nilai array/slice namun dalam bentuk key-value. + +```go +// cara vertikal +var chicken1 = map[string]int{"januari": 50, "februari": 40} + +// cara horizontal +var chicken2 = map[string]int{ + "januari": 50, + "februari": 40, +} +``` + +Key dan value dituliskan dengan pembatas tanda titik dua (`:`). Sedangkan tiap itemnya dituliskan dengan pembatas tanda koma (`,`). Khusus deklarasi dengan gaya vertikal, tanda koma perlu dituliskan setelah item terakhir. + +Variabel `map` bisa di-inisialisasi dengan tanpa nilai awal, caranya menggunakan tanda kurung kurawal, contoh: `map[string]int{}`. Atau bisa juga dengan menggunakan keyword `make` dan `new`. Contohnya bisa dilihat pada kode berikut. Ketiga cara di bawah ini intinya adalah sama. + +```go +var chicken3 = map[string]int{} +var chicken4 = make(map[string]int) +var chicken5 = *new(map[string]int) +``` + +Khusus inisialisasi data menggunakan keyword `new`, yang dihasilkan adalah data pointer. Untuk mengambil nilai aslinya bisa dengan menggunakan tanda asterisk (`*`). Topik pointer akan dibahas lebih detail ketika sudah masuk bab 22. + +## A.16.3. Iterasi Item Map Menggunakan `for` - `range` + +Item variabel `map` bisa di iterasi menggunakan `for` - `range`. Cara penerapannya masih sama seperti pada slice, pembedanya data yang dikembalikan di tiap perulangan adalah key dan value, bukan indeks dan elemen. Contohnya bisa dilihat pada kode berikut. + +```go +var chicken = map[string]int{ + "januari": 50, + "februari": 40, + "maret": 34, + "april": 67, +} + +for key, val := range chicken { + fmt.Println(key, " \t:", val) +} +``` + +![Perulangan Map](images/A.16_2_map_for_range.png) + +## A.16.4. Menghapus Item Map + +Fungsi `delete()` digunakan untuk menghapus item dengan key tertentu pada variabel map. Cara penggunaannya, dengan memasukan objek map dan key item yang ingin dihapus sebagai parameter. + +```go +var chicken = map[string]int{"januari": 50, "februari": 40} + +fmt.Println(len(chicken)) // 2 +fmt.Println(chicken) + +delete(chicken, "januari") + +fmt.Println(len(chicken)) // 1 +fmt.Println(chicken) +``` + +Item yang memiliki key `"januari"` dalam variabel `chicken` akan dihapus. + +![Hapus item Map](images/A.16_3_map_delete_item.png) + +Fungsi `len()` jika digunakan pada map akan mengembalikan jumlah item. + +## A.16.5. Deteksi Keberadaan Item Dengan Key Tertentu + +Ada cara untuk mengetahui apakah dalam sebuah variabel map terdapat item dengan key tertentu atau tidak, yaitu dengan memanfaatkan 2 variabel sebagai penampung nilai kembalian pengaksesan item. Return value ke-2 ini adalah opsional, isinya nilai `bool` yang menunjukkan ada atau tidaknya item yang dicari. + +```go +var chicken = map[string]int{"januari": 50, "februari": 40} +var value, isExist = chicken["mei"] + +if isExist { + fmt.Println(value) +} else { + fmt.Println("item is not exists") +} +``` + +## A.16.6. Kombinasi Slice & Map + +Slice dan `map` bisa dikombinasikan, dan sering digunakan pada banyak kasus, contohnya seperti data array yang berisikan informasi siswa, dan banyak lainnya. + +Cara menggunakannya cukup mudah, contohnya seperti `[]map[string]int`, artinya slice yang tipe tiap elemen-nya adalah `map[string]int`. + +Agar lebih jelas, silakan praktekan contoh berikut. + +```go +var chickens = []map[string]string{ + map[string]string{"name": "chicken blue", "gender": "male"}, + map[string]string{"name": "chicken red", "gender": "male"}, + map[string]string{"name": "chicken yellow", "gender": "female"}, +} + +for _, chicken := range chickens { + fmt.Println(chicken["gender"], chicken["name"]) +} +``` + +Variabel `chickens` di atas berisikan informasi bertipe `map[string]string`, yang kebetulan tiap elemen memiliki 2 key yang sama. + +Jika anda menggunakan versi go terbaru, cara deklarasi slice-map bisa dipersingkat, tipe tiap elemen tidak wajib untuk dituliskan. + +```go +var chickens = []map[string]string{ + {"name": "chicken blue", "gender": "male"}, + {"name": "chicken red", "gender": "male"}, + {"name": "chicken yellow", "gender": "female"}, +} +``` + +Dalam `[]map[string]string`, tiap elemen bisa saja memiliki key yang berbeda-beda, sebagai contoh seperti kode berikut. + +```go +var data = []map[string]string{ + {"name": "chicken blue", "gender": "male", "color": "brown"}, + {"address": "mangga street", "id": "k001"}, + {"community": "chicken lovers"} +} +``` + +--- + + diff --git a/17-fungsi.md b/17-fungsi.md new file mode 100644 index 000000000..6b8e3d850 --- /dev/null +++ b/17-fungsi.md @@ -0,0 +1,173 @@ +# A.17. Fungsi + +Fungsi merupakan aspek penting dalam pemrograman. Definisi fungsi sendiri adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (kependekan dari *don't repeat yourself*), tak perlu menuliskan banyak kode yang kegunaannya berkali-kali, cukup sekali saja lalu panggil sesuai kebutuhan. + +Di bab ini kita akan belajar tentang penggunaan fungsi di Go. + +## A.17.1. Penerapan Fungsi + +Sebenarnya tanpa sadar, kita sudah menerapkan fungsi di bab-bab sebelum ini, yaitu pada fungsi `main`. Fungsi `main` merupakan fungsi yang paling utama pada program Go. + +Cara membuat fungsi cukup mudah, yaitu dengan menuliskan keyword `func`, diikuti setelahnya nama fungsi, kurung yang berisikan parameter, dan kurung kurawal untuk membungkus blok kode. + +Parameter sendiri adalah variabel yang disisipkan pada saat pemanggilan fungsi. + +Silakan lihat dan praktekan kode tentang implementasi fungsi berikut. + +```go +package main + +import "fmt" +import "strings" + +func main() { + var names = []string{"John", "Wick"} + printMessage("halo", names) +} + +func printMessage(message string, arr []string) { + var nameString = strings.Join(arr, " ") + fmt.Println(message, nameString) +} +``` + +Pada kode di atas, sebuah fungsi baru dibuat dengan nama `printMessage` memiliki 2 buah parameter yaitu string `message` dan slice string `arr`. + +Fungsi tersebut dipanggil dalam `main`, dengan disisipkan 2 buah data sebagai parameter, data pertama adalah string `"hallo"` yang ditampung parameter `message`, dan parameter ke 2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr`. + +Di dalam `printMessage`, nilai `arr` yang merupakan slice string digabungkan menjadi sebuah string dengan pembatas adalah karakter **spasi**. Penggabungan slice dapat dilakukan dengan memanfaatkan fungsi `strings.Join()` (berada di dalam package `strings`). + +![Contoh penggunaan fungsi](images/A.17_1_function.png) + +## A.17.2. Fungsi Dengan Return Value / Nilai Balik + +Sebuah fungsi bisa didesain tidak mengembalikan nilai balik (*void*), atau bisa mengembalikan suatu nilai. Fungsi yang memiliki nilai kembalian, harus ditentukan tipe data nilai baliknya pada saat deklarasi. + +Program berikut merupakan contoh penerapan fungsi yang memiliki return value. + +```go +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func main() { + rand.Seed(time.Now().Unix()) + var randomValue int + + randomValue = randomWithRange(2, 10) + fmt.Println("random number:", randomValue) + randomValue = randomWithRange(2, 10) + fmt.Println("random number:", randomValue) + randomValue = randomWithRange(2, 10) + fmt.Println("random number:", randomValue) +} + +func randomWithRange(min, max int) int { + var value = rand.Int() % (max - min + 1) + min + return value +} + +``` + +Fungsi `randomWithRange` bertugas untuk *generate* angka acak sesuai dengan range yang ditentukan, yang kemudian angka tersebut dijadikan nilai kembalian fungsi. + +![Fungsi dengan nilai balik](images/A.17_2_function_return_type.png) + +Cara menentukan tipe data nilai balik fungsi adalah dengan menuliskan tipe data yang diinginkan setelah kurung parameter. Bisa dilihat pada kode di atas, bahwa `int` merupakan tipe data nilai balik fungsi `randomWithRange`. + +```go +func randomWithRange(min, max int) int +``` + +Sedangkan cara untuk mengembalikan nilai itu sendiri adalah dengan menggunakan keyword `return` diikuti data yang ingin dikembalikan. Pada contoh di atas, `return value` artinya nilai variabel `value` dijadikan nilai kembalian fungsi. + +Eksekusi keyword `return` akan menjadikan proses dalam blok fungsi berhenti pada saat itu juga. Semua statement setelah keyword tersebut tidak akan dieksekusi. + +--- + +Dari kode di atas mungkin ada beberapa hal yang belum pernah kita lakukan pada bab-bab sebelumnya, kita akan bahas satu-persatu. + +## A.17.3. Penggunaan Fungsi `rand.Seed()` + +Fungsi ini diperlukan untuk memastikan bahwa angka random yang akan di-generate benar-benar acak. Kita bisa gunakan angka apa saja sebagai nilai parameter fungsi ini (umumnya diisi `time.Now().Unix()`). + +```go +rand.Seed(time.Now().Unix()) +``` + +Fungsi `rand.Seed()` berada dalam package `math/rand`, yang harus di-import terlebih dahulu sebelum bisa dimanfaatkan. + +Package `time` juga perlu di-import karena kita menggunakan fungsi `(time.Now().Unix())` disitu. + +## A.17.4. Import Banyak Package + +Penulisan keyword `import` untuk banyak package bisa dilakukan dengan dua cara, dengan menuliskannya di tiap package, atau cukup sekali saja, bebas. + +```go +import "fmt" +import "math/rand" +import "time" + +// atau + +import ( + "fmt" + "math/rand" + "time" +) +``` + +## A.17.5. Deklarasi Parameter Bertipe Data Sama + +Khusus untuk fungsi yang tipe data parameternya sama, bisa ditulis dengan gaya yang unik. Tipe datanya dituliskan cukup sekali saja di akhir. Contohnya bisa dilihat pada kode berikut. + +```go +func nameOfFunc(paramA type, paramB type, paramC type) returnType +func nameOfFunc(paramA, paramB, paramC type) returnType + +func randomWithRange(min int, max int) int +func randomWithRange(min, max int) int +``` + +## A.17.6. Penggunaan Keyword `return` Untuk Menghentikan Proses Dalam Fungsi + +Selain sebagai penanda nilai balik, keyword `return` juga bisa dimanfaatkan untuk menghentikan proses dalam blok fungsi dimana ia dipakai. Contohnya bisa dilihat pada kode berikut. + +```go +package main + +import "fmt" + +func main() { + divideNumber(10, 2) + divideNumber(4, 0) + divideNumber(8, -4) +} + +func divideNumber(m, n int) { + if n == 0 { + fmt.Printf("invalid divider. %d cannot divided by %d\n", m, n) + return + } + + var res = m / n + fmt.Printf("%d / %d = %d\n", m, n, res) +} +``` + +Fungsi `divideNumber` didesain tidak memiliki nilai balik. Fungsi ini dibuat untuk membungkus proses pembagian 2 bilangan, lalu menampilkan hasilnya. + +Didalamnya terdapat proses validasi nilai variabel pembagi, jika nilainya adalah 0, maka akan ditampilkan pesan bahwa pembagian tidak bisa dilakukan, lalu proses dihentikan pada saat itu juga (dengan memanfaatkan keyword `return`). Jika nilai pembagi valid, maka proses pembagian diteruskan. + +![Keyword return menjadikan proses dalam fungsi berhenti](images/A.17_3_function_return_as_break.png) + +--- + + diff --git a/18-fungsi-multiple-return.md b/18-fungsi-multiple-return.md new file mode 100644 index 000000000..c1e8ebcd1 --- /dev/null +++ b/18-fungsi-multiple-return.md @@ -0,0 +1,104 @@ +# A.18. Fungsi Multiple Return + +Umumnya fungsi hanya memiliki satu buah nilai balik saja. Jika ada kebutuhan dimana data yang dikembalikan harus banyak, biasanya digunakanlah tipe seperti `map`, slice, atau `struct` sebagai nilai balik. + +Go menyediakan kapabilitas bagi programmer untuk membuat fungsi memiliki banyak nilai balik. Di bab ini akan dibahas bagaimana penerapannya. + +## A.18.1 Penerapan Fungsi Multiple Return + +Cara membuat fungsi yang memiliki banyak nilai balik tidaklah sulit. Tinggal tulis saja pada saat deklarasi fungsi semua tipe data nilai yang dikembalikan, dan pada keyword `return` tulis semua data yang ingin dikembalikan. Contoh bisa dilihat pada berikut. + +```go +package main + +import "fmt" +import "math" + +func calculate(d float64) (float64, float64) { + // hitung luas + var area = math.Pi * math.Pow(d / 2, 2) + // hitung keliling + var circumference = math.Pi * d + + // kembalikan 2 nilai + return area, circumference +} +``` + +Fungsi `calculate()` di atas menerima satu buah parameter (`diameter`) yang digunakan dalam proses perhitungan. Di dalam fungsi tersebut ada 2 hal yang dihitung, yaitu nilai **keliling** dan **lingkaran**. Kedua nilai tersebut kemudian dijadikan sebagai return value fungsi. + +Cara pendefinisian banyak nilai balik bisa dilihat pada kode di atas, langsung tulis tipe data semua nilai balik dipisah tanda koma, lalu ditambahkan kurung diantaranya. + +```go +func calculate(d float64) (float64, float64) +``` + +Tak lupa di bagian penulisan keyword `return` harus dituliskan juga semua data yang dijadikan nilai balik (dengan pemisah tanda koma). + +```go +return area, circumference +``` + +Implementasi dari fungsi `calculate()` di atas, bisa dilihat pada kode berikut. + +```go +func main() { + var diameter float64 = 15 + var area, circumference = calculate(diameter) + + fmt.Printf("luas lingkaran\t\t: %.2f \n", area) + fmt.Printf("keliling lingkaran\t: %.2f \n", circumference) +} +``` + +Output program: + +![Penerapan teknik multiple return](images/A.18_1_multiple_return.png) + +Karena fungsi tersebut memiliki banyak nilai balik, maka pada pemanggilannya harus disiapkan juga banyak variabel untuk menampung nilai kembalian yang ada (sesuai jumlah nilai balik fungsi). + +```go +var area, circumference = calculate(diameter) +``` + +## A.18.2 Fungsi Dengan Predefined Return Value + +Keunikan lainnya yang jarang ditemui di bahasa lain adalah, di Go variabel yang digunakan sebagai nilai balik bisa didefinisikan di-awal. + +```go +func calculate(d float64) (area float64, circumference float64) { + area = math.Pi * math.Pow(d / 2, 2) + circumference = math.Pi * d + + return +} +``` + +Fungsi `calculate` kita modif menjadi lebih sederhana. Bisa dilihat di kode di atas, ada cukup banyak perbedaan dibanding fungsi `calculate` sebelumnya. Perhatikan kode berikut. + +```go +func calculate(d float64) (area float64, circumference float64) { +``` + +Fungsi dideklarasikan memiliki 2 buah tipe data, dan variabel yang nantinya dijadikan nilai balik juga dideklarasikan. Variabel `area` yang bertipe `float64`, dan `circumference` bertipe `float64`. + +Karena variabel nilai balik sudah ditentukan di awal, untuk mengembalikan nilai cukup dengan memanggil `return` tanpa perlu diikuti variabel apapun. Nilai terakhir `area` dan `circumference` sebelum pemanggilan keyword `return` adalah hasil dari fungsi di atas. + +--- + +Ada beberapa hal baru dari kode di atas yang perlu dibahas, seperti `math.Pow()` dan `math.Pi`. Berikut adalah penjelasannya. + +### A.18.2.1. Penggunaan Fungsi `math.Pow()` + +Fungsi `math.Pow()` digunakan untuk memangkat nilai. `math.Pow(2, 3)` berarti 2 pangkat 3, hasilnya 8. Fungsi ini berada dalam package `math`. + +### A.18.2.2. Penggunaan Konstanta `math.Pi` + +`math.Pi` adalah konstanta bawaan `package math` yang merepresentasikan **Pi** atau **22/7**. + +--- + + diff --git a/19-fungsi-variadic.md b/19-fungsi-variadic.md new file mode 100644 index 000000000..5980ad25d --- /dev/null +++ b/19-fungsi-variadic.md @@ -0,0 +1,152 @@ +# A.19. Fungsi Variadic + +Go mengadopsi konsep **variadic function** atau pembuatan fungsi dengan parameter sejenis yang tak terbatas. Maksud **tak terbatas** disini adalah jumlah parameter yang disisipkan ketika pemanggilan fungsi bisa berapa saja. + +Parameter variadic memiliki sifat yang mirip dengan slice. Nilai dari parameter-parameter yang disisipkan bertipe data sama, dan ditampung oleh sebuah variabel saja. Cara pengaksesan tiap datanya juga sama, dengan menggunakan index. + +Di bab ini kita akan belajar mengenai cara penerapan fungsi variadic. + +## A.19.1. Penerapan Fungsi Variadic + +Deklarasi parameter variadic sama dengan cara deklarasi variabel biasa, pembedanya adalah pada parameter jenis ini ditambahkan tanda titik tiga kali (`...`) tepat setelah penulisan variabel (sebelum tipe data). Nantinya semua nilai yang disisipkan sebagai parameter akan ditampung oleh variabel tersebut. + +Berikut merupakan contoh penerepannya. + +```go +package main + +import "fmt" + +func main() { + var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3) + var msg = fmt.Sprintf("Rata-rata : %.2f", avg) + fmt.Println(msg) +} + +func calculate(numbers ...int) float64 { + var total int = 0 + for _, number := range numbers { + total += number + } + + var avg = float64(total) / float64(len(numbers)) + return avg +} +``` + +Output program: + +![Contoh penerapan parameter variadic](images/A.19_1_variadic_param.png) + +Bisa dilihat pada fungsi `calculate()`, parameter `numbers` dideklarasikan dengan disisipkan tanda 3 titik (`...`), menandakan bahwa `numbers` adalah sebuah parameter variadic dengan tipe data `int`. + +```go +func calculate(numbers ...int) float64 { +``` + +Pemanggilan fungsi dilakukan seperti biasa, hanya saja jumlah parameter yang disisipkan bisa banyak. + +```go +var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3) +``` + +Nilai tiap parameter bisa diakses seperti cara pengaksesan tiap elemen slice. Pada contoh di atas metode yang dipilih adalah `for` - `range`. + +```go +for _, number := range numbers { +``` + +--- + +Berikut merupakan penjelasan tambahan dari kode yang telah kita tulis. + +### A.19.1.1. Penggunaan Fungsi `fmt.Sprintf()` + +Fungsi `fmt.Sprintf()` pada dasarnya sama dengan `fmt.Printf()`, hanya saja fungsi ini tidak menampilkan nilai, melainkan mengembalikan nilainya dalam bentuk string. Pada kasus di atas, nilai kembalian `fmt.Sprintf()` ditampung oleh variabel `msg`. + +Selain `fmt.Sprintf()`, ada juga `fmt.Sprint()` dan `fmt.Sprintln()`. + +### A.19.1.2. Penggunaan Fungsi `float64()` + +Sebelumnya sudah dibahas bahwa `float64` merupakan tipe data. Tipe data jika ditulis sebagai fungsi (penandanya ada tanda kurungnya) berguna untuk **casting**. Casting sendiri adalah teknik untuk konversi tipe sebuah data ke tipe lain. Hampir semua jenis tipe data dasar yang telah dipelajari di bab 9 bisa digunakan untuk casting. Dan cara penerepannya juga sama, cukup panggil sebagai fungsi, lalu masukan data yang ingin dikonversi sebagai parameter. + +Pada contoh di atas, variabel `total` yang tipenya adalah `int`, dikonversi menjadi `float64`, begitu juga `len(numbers)` yang menghasilkan `int` dikonversi ke `float64`. + +Variabel `avg` perlu dijadikan `float64` karena penghitungan rata-rata lebih sering menghasilkan nilai desimal. + +Operasi bilangan (perkalian, pembagian, dan lainnya) di Go hanya bisa dilakukan jika tipe datanya sejenis. Maka dari itulah perlu adanya casting ke tipe `float64` pada tiap operand. + +--- + +## A.19.2. Pengisian Parameter Fungsi Variadic Menggunakan Data Slice + +Slice bisa digunakan sebagai parameter variadic. Caranya dengan menambahkan tanda titik tiga kali, tepat setelah nama variabel yang dijadikan parameter. Contohnya bisa dilihat pada kode berikut. + +```go +var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3} +var avg = calculate(numbers...) +var msg = fmt.Sprintf("Rata-rata : %.2f", avg) + +fmt.Println(msg) +``` + +Pada kode di atas, variabel `numbers` yang merupakan slice int, disisipkan ke fungsi `calculate()` sebagai parameter variadic (bisa dilihat tanda 3 titik setelah penulisan variabel). Teknik ini sangat berguna ketika sebuah data slice ingin difungsikan sebagai parameter variadic. + +Perhatikan juga kode berikut ini. Intinya adalah sama, hanya caranya yang berbeda. + +```go +var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3} +var avg = calculate(numbers...) + +// atau + +var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3) +``` + +Pada deklarasi parameter fungsi variadic, tanda 3 titik (`...`) dituliskan sebelum tipe data parameter. Sedangkan pada pemanggilan fungsi dengan menyisipkan parameter array, tanda tersebut dituliskan dibelakang variabelnya. + +## A.19.3. Fungsi Dengan Parameter Biasa & Variadic + +Parameter variadic bisa dikombinasikan dengan parameter biasa, dengan syarat parameter variadic-nya harus diposisikan di akhir. Contohnya bisa dilihat pada kode berikut. + +```go +import "fmt" +import "strings" + +func yourHobbies(name string, hobbies ...string) { + var hobbiesAsString = strings.Join(hobbies, ", ") + + fmt.Printf("Hello, my name is: %s\n", name) + fmt.Printf("My hobbies are: %s\n", hobbiesAsString) +} +``` + +Nilai parameter pertama fungsi `yourHobbies()` akan ditampung oleh `name`, sedangkan nilai parameter kedua dan seterusnya akan ditampung oleh `hobbies` sebagai slice. + +Cara pemanggilannya masih sama seperi pada fungsi biasa. + +```go +func main() { + yourHobbies("wick", "sleeping", "eating") +} +``` + +Jika parameter kedua dan seterusnya ingin diisi dengan data dari slice, maka gunakan tanda titik tiga kali. + +```go +func main() { + var hobbies = []string{"sleeping", "eating"} + yourHobbies("wick", hobbies...) +} +``` + +Output program: + +![Kombinasi parameter biasa dan variadic](images/A.19_2_parameter_combination.png) + +--- + + diff --git a/2-instalasi-golang.md b/2-instalasi-golang.md new file mode 100644 index 000000000..bca840c27 --- /dev/null +++ b/2-instalasi-golang.md @@ -0,0 +1,91 @@ +# A.2. Instalasi Golang + +Hal pertama yang perlu dilakukan sebelum bisa menggunakan Go adalah meng-install-nya terlebih dahulu. Panduan instalasi sebenarnya sudah disediakan di situs official Golang [http://golang.org/doc/install#install](http://golang.org/doc/install#install). + +Disini penulis mencoba meringkas petunjuk instalasi di link tersebut, agar lebih mudah untuk diikuti terutama untuk pembaca yang baru belajar. + +> Go yang digunakan adalah versi **1.12.7**. Direkomendasikan menggunakan versi tersebut, atau versi lain minimal **1.11** ke atas. + +Link untuk download installer go: https://golang.org/dl/. Anda bisa langsung unduh dari URL tersebut lalu lakukan instalasi sendiri, atau bisa mengikuti petunjuk di bab ini. + +## A.2.1. Instalasi Go di Windows + + 1. Download terlebih dahulu installer-nya di [https://golang.org/dl/](https://golang.org/dl/). Pilih installer untuk OS Windows sesuai jenis bit yang digunakan. + + 2. Setelah ter-download, jalankan installer, klik **next** sampai proses instalasi selesai. By default jika anda tidak merubah path pada saat instalasi, Golang akan terinstall di terinstal di `C:\go`. Path tersebut secara otomatis didaftarkan dalam **path variable**. + + 3. Buka **Command Prompt** / **CMD**, eksekusi perintah untuk mengecek versi Go. + + ```bash + $ go version + ``` + + 4. Jika output adalah sama dengan Go yang ter-install, menandakan instalasi berhasil. + +> Sering terjadi command `go version` tidak bisa dijalankan meskipun instalasi sukses. Solusinya bisa dengan restart CMD (close CMD, lalu buka kembali). Setelah itu coba jalankan sekali lagi command tersebut. + +## A.2.2. Instalasi Go di Mac OS + +Cara termudah instalasi Go di **Mac OS** adalah menggunakan [homebrew](http://brew.sh/). + + 1. Install terlebih dahulu Homebrew (jika belum ada), jalankan perintah tersebut di **terminal**. + + ```bash + $ ruby -e "$(curl -fsSL http://git.io/pVOl)" + ``` + + 2. Install Go menggunakan command `brew`. + + ```bash + $ brew install go + ``` + + 3. Tambahkan path ke environment variable. + + ```bash + $ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bash_profile + $ source ~/.bash_profile + ``` + + 4. Jalankan command untuk mengecek versi Go. + + ```bash + $ go version + ``` + + 5. Jika output adalah sama dengan Go yang ter-install, menandakan instalasi berhasil. + +## A.2.3. Instalasi Go di Linux + + 1. Download archive installer dari link [https://golang.org/dl/](https://golang.org/dl/), pilih installer linux yg sesuai dengan jenis bit komputer anda. Download bisa dilakukan lewat CLI, menggunakan `wget` atau `curl`. + + ```bash + $ wget https://storage.googleapis.com/golang/go1... + ``` + + 2. Buka **terminal**, extract archive tersebut ke `/usr/local`. + + ```bash + $ tar -C /usr/local -xzf go1... + ``` + + 3. Setelah itu export path-nya, gunakan command di bawah ini. + + ```bash + $ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc + $ source ~/.bashrc + ``` + + 4. Selanjutnya, eksekusi perintah berikut untuk mengetes apakah Go sudah terinstal dengan benar. + + ```bash + $ go version + ``` + + 5. Jika output adalah sama dengan Go yang ter-install, menandakan instalasi berhasil. + +## A.2.4. Variabel `GOROOT` + +By default, setelah proses instalasi Go selesai, secara otomatis akan muncul environment variabel bernama `GOROOT`. Isi dari environment variabel ini adalah lokasi dimana Go ter-install. + +Sebagai contoh di Windows, ketika Go di-install di `C:\go`, maka path tersebut akan menjadi isi dari `GOROOT`. diff --git a/20-fungsi-closure.md b/20-fungsi-closure.md new file mode 100644 index 000000000..c0797319e --- /dev/null +++ b/20-fungsi-closure.md @@ -0,0 +1,177 @@ +# A.20. Fungsi Closure + +Definisi **Closure** adalah sebuah fungsi yang bisa disimpan dalam variabel. Dengan menerapkan konsep tersebut, kita bisa membuat fungsi didalam fungsi, atau bahkan membuat fungsi yang mengembalikan fungsi. + +Closure merupakan *anonymous function* atau fungsi tanpa nama. Biasa dimanfaatkan untuk membungkus suatu proses yang hanya dipakai sekali atau dipakai pada blok tertentu saja. + +## A.20.1. Closure Disimpan Sebagai Variabel + +Sebuah fungsi tanpa nama bisa disimpan dalam variabel. Variabel yang menyimpan closure memiliki sifat seperti fungsi yang disimpannya. Di bawah ini adalah contoh program sederhana untuk mencari nilai terendah dan tertinggi dari suatu array. Logika pencarian dibungkus dalam closure yang ditampung oleh variabel `getMinMax`. + +```go +package main + +import "fmt" + +func main() { + var getMinMax = func(n []int) (int, int) { + var min, max int + for i, e := range n { + switch { + case i == 0: + max, min = e, e + case e > max: + max = e + case e < min: + min = e + } + } + return min, max + } + + var numbers = []int{2, 3, 4, 3, 4, 2, 3} + var min, max = getMinMax(numbers) + fmt.Printf("data : %v\nmin : %v\nmax : %v\n", numbers, min, max) +} +``` + + +Bisa dilihat pada kode di atas bagiamana sebuah closure dibuat dan dipanggil. Sedikit berbeda memang dibanding pembuatan fungsi biasa. Fungsi ditulis tanpa nama, lalu ditampung dalam variabel. + +```go +var getMinMax = func(n []int) (int, int) { + // ... +} +``` + +Cara pemanggilannya, dengan menuliskan nama variabel tersebut sebagai fungsi, seperti pemanggilan fungsi biasa. + +```go +var min, max = getMinMax(numbers) +``` + +Output program: + +![Penerapan closure](images/A.20_1_closure.png) + +--- + +Berikut adalah penjelasan tambahan mengenai kode di atas + +## A.20.1.1. Penggunaan Template String `%v` + +Template `%v` digunakan untuk menampilkan segala jenis data. Bisa array, int, float, bool, dan lainnya. + +```go +fmt.Printf("data : %v\nmin : %v\nmax : %v\n", numbers, min, max) +``` + +Bisa dilihat pada statement di atas, data bertipe array dan numerik ditampilkan menggunakan `%v`. Template ini biasa dimanfaatkan untuk menampilkan sebuah data yang tipe nya bisa dinamis atau belum diketahui. Sangat tepat jika digunakan pada data bertipe `interface{}` yang nantinya akan di bahas pada bab 27. + +--- + +## A.20.2. Immediately-Invoked Function Expression (IIFE) + +Closure jenis ini dieksekusi langsung pada saat deklarasinya. Biasa digunakan untuk membungkus proses yang hanya dilakukan sekali, bisa mengembalikan nilai, bisa juga tidak. + +Di bawah ini merupakan contoh sederhana penerapan metode IIFE untuk filtering data array. + +```go +package main + +import "fmt" + +func main() { + var numbers = []int{2, 3, 0, 4, 3, 2, 0, 4, 2, 0, 3} + + var newNumbers = func(min int) []int { + var r []int + for _, e := range numbers { + if e < min { + continue + } + r = append(r, e) + } + return r + }(3) + + fmt.Println("original number :", numbers) + fmt.Println("filtered number :", newNumbers) +} +``` + +Output program: + +![Penerapan IIFE](images/A.20_2_iife.png) + +Ciri khas IIFE adalah adanya kurung parameter tepat setelah deklarasi closure berakhir. Jika ada parameter, bisa juga dituliskan dalam kurung parameternya. + +```go +var newNumbers = func(min int) []int { + // ... +}(3) +``` + +Pada contoh di atas IIFE menghasilkan nilai balik yang kemudian ditampung `newNumber`. Perlu diperhatikan bahwa yang ditampung adalah **nilai kembaliannya** bukan body fungsi atau **closure**. + +> Closure bisa juga dengan gaya manifest typing, caranya dengan menuliskan skema closure-nya sebagai tipe data. Contoh:
var closure (func (string, int, []string) int)
closure = func (a string, b int, c []string) int {
    // ..
} + +## A.20.3. Closure Sebagai Nilai Kembalian + +Salah satu keunikan closure lainnya adalah bisa dijadikan sebagai nilai balik fungsi, cukup aneh memang, tapi pada suatu kondisi teknik ini sangat membantu. Di bawah ini disediakan sebuah fungsi bernama `findMax()`, fungsi ini salah satu nilai kembaliannya berupa closure. + +```go +package main + +import "fmt" + +func findMax(numbers []int, max int) (int, func() []int) { + var res []int + for _, e := range numbers { + if e <= max { + res = append(res, e) + } + } + return len(res), func() []int { + return res + } +} +``` + +Nilai kembalian ke-2 pada fungsi di atas adalah closure dengan skema `func() []int`. Bisa dilihat di bagian akhir, ada fungsi tanpa nama yang dikembalikan. + +```go +return len(res), func() []int { + return res +} +``` + +> Fungsi tanpa nama yang akan dikembalikan boleh disimpan pada variabel terlebih dahulu. Contohnya:
var getNumbers = func() []int {
    return res
}
return len(res), getNumbers + +Sedikit tentang fungsi `findMax()`, fungsi ini digunakan untuk mencari banyaknya angka-angka yang nilainya di bawah atau sama dengan angka tertentu. Nilai kembalian pertama adalah jumlah angkanya. Nilai kembalian kedua berupa closure yang mengembalikan angka-angka yang dicari. Berikut merupakan contoh implementasi fungsi tersebut. + +```go +func main() { + var max = 3 + var numbers = []int{2, 3, 0, 4, 3, 2, 0, 4, 2, 0, 3} + var howMany, getNumbers = findMax(numbers, max) + var theNumbers = getNumbers() + + fmt.Println("numbers\t:", numbers) + fmt.Printf("find \t: %d\n\n", max) + + fmt.Println("found \t:", howMany) // 9 + fmt.Println("value \t:", theNumbers) // [2 3 0 3 2 0 2 0 3] +} +``` + +Output program: + +![Kombinasi parameter biasa dan variadic](images/A.20_3_combination.png) + +--- + + diff --git a/21-fungsi-sebagai-parameter.md b/21-fungsi-sebagai-parameter.md new file mode 100644 index 000000000..944068a79 --- /dev/null +++ b/21-fungsi-sebagai-parameter.md @@ -0,0 +1,105 @@ +# A.21. Fungsi Sebagai parameter + +Setelah di bab sebelumnya kita belajar mengenai fungsi yang mengembalikan nilai balik berupa fungsi, kali ini topiknya tidak kalah unik, yaitu fungsi yang digunakan sebagai parameter. + +Di Go, fungsi bisa dijadikan sebagai tipe data variabel. Dari situ sangat memungkinkan untuk menjadikannya sebagai parameter juga. + +## A.21.1. Penerapan Fungsi Sebagai Parameter + +Cara membuat parameter fungsi adalah dengan langsung menuliskan skema fungsi nya sebagai tipe data. Contohnya bisa dilihat pada kode berikut. + +```go +package main + +import "fmt" +import "strings" + +func filter(data []string, callback func(string) bool) []string { + var result []string + for _, each := range data { + if filtered := callback(each); filtered { + result = append(result, each) + } + } + return result +} +``` + +Parameter `callback` merupakan sebuah closure yang dideklarasikan bertipe `func(string) bool`. Closure tersebut dipanggil di tiap perulangan dalam fungsi `filter()`. + +Fungsi `filter()` sendiri kita buat untuk filtering data array (yang datanya didapat dari parameter pertama), dengan kondisi filter bisa ditentukan sendiri. Di bawah ini adalah contoh pemanfaatan fungsi tersebut. + +```go +func main() { + var data = []string{"wick", "jason", "ethan"} + var dataContainsO = filter(data, func(each string) bool { + return strings.Contains(each, "o") + }) + var dataLenght5 = filter(data, func(each string) bool { + return len(each) == 5 + }) + + fmt.Println("data asli \t\t:", data) + // data asli : [wick jason ethan] + + fmt.Println("filter ada huruf \"o\"\t:", dataContainsO) + // filter ada huruf "o" : [jason] + + fmt.Println("filter jumlah huruf \"5\"\t:", dataLenght5) + // filter jumlah huruf "5" : [jason ethan] +} +``` + +Ada cukup banyak hal yang terjadi didalam tiap pemanggilan fungsi `filter()` di atas. Berikut merupakan penjelasannya. + + 1. Data array (yang didapat dari parameter pertama) akan di-looping. + 2. Di tiap perulangannya, closure `callback` dipanggil, dengan disisipkan data tiap elemen perulangan sebagai parameter. + 3. Closure `callback` berisikan kondisi filtering, dengan hasil bertipe `bool` yang kemudian dijadikan nilai balik dikembalikan. + 5. Di dalam fungsi `filter()` sendiri, ada proses seleksi kondisi (yang nilainya didapat dari hasil eksekusi closure `callback`). Ketika kondisinya bernilai `true`, maka data elemen yang sedang diulang dinyatakan lolos proses filtering. + 6. Data yang lolos ditampung variabel `result`. Variabel tersebut dijadikan sebagai nilai balik fungsi `filter()`. + +![Filtering data](images/A.21_1_filtering.png) + +Pada `dataContainsO`, parameter kedua fungsi `filter()` berisikan statement untuk deteksi apakah terdapat substring `"o"` di dalam nilai variabel `each` (yang merupakan data tiap elemen), jika iya, maka kondisi filter bernilai `true`, dan sebaliknya. + +pada contoh ke-2 (`dataLength5`), closure `callback` berisikan statement untuk deteksi jumlah karakter tiap elemen. Jika ada elemen yang jumlah karakternya adalah 5, berarti elemen tersebut lolos filter. + +Memang butuh usaha ekstra untuk memahami pemanfaatan closure sebagai parameter fungsi. Tapi setelah paham, penerapan teknik ini pada kondisi yang tepat akan sangat membantu proses pembuatan aplikasi. + +## A.21.2. Alias Skema Closure + +Kita sudah mempelajari bahwa closure bisa dimanfaatkan sebagai tipe parameter, contohnya seperti pada fungsi `filter()`. Pada fungsi tersebut kebetulan skema tipe parameter closure-nya tidak terlalu panjang, hanya ada satu buah parameter dan satu buah nilai balik. + +Pada fungsi yang skema-nya cukup panjang, akan lebih baik jika menggunakan alias, apalagi ketika ada parameter fungsi lain yang juga menggunakan skema yang sama. Membuat alias fungsi berarti menjadikan skema fungsi tersebut menjadi tipe data baru. Caranya dengan menggunakan keyword `type`. Contoh: + +```go +type FilterCallback func(string) bool + +func filter(data []string, callback FilterCallback) []string { + // ... +} +``` + +Skema `func(string) bool` diubah menjadi tipe dengan nama `FilterCallback`. Tipe tersebut kemudian digunakan sebagai tipe data parameter `callback`. + +--- + +Di bawah ini merupakan penjelasan tambahan mengenai fungsi `strings.Contains()`. + +## A.21.2.1. Penggunaan Fungsi `string.Contains()` + +Inti dari fungsi ini adalah untuk deteksi apakah sebuah substring adalah bagian dari string, jika iya maka akan bernilai `true`, dan sebaliknya. Contoh penggunaannya: + +```go +var result = strings.Contains("Golang", "ang") +// true +``` + +Variabel `result` bernilai `true` karena string `"ang"` merupakan bagian dari string `"Golang"`. + +--- + + diff --git a/22-pointer.md b/22-pointer.md new file mode 100644 index 000000000..f0f35204f --- /dev/null +++ b/22-pointer.md @@ -0,0 +1,106 @@ +# A.22. Pointer + +Pointer adalah *reference* atau alamat memori. Variabel pointer berarti variabel yang berisi alamat memori suatu nilai. Sebagai contoh sebuah variabel bertipe integer memiliki nilai **4**, maka yang dimaksud pointer adalah **alamat memori dimana nilai 4 disimpan**, bukan nilai 4 itu sendiri. + +Variabel-variabel yang memiliki *reference* atau alamat pointer yang sama, saling berhubungan satu sama lain dan nilainya pasti sama. Ketika ada perubahan nilai, maka akan memberikan efek kepada variabel lain (yang referensi-nya sama) yaitu nilainya ikut berubah. + +## A.22.1. Penerapan Pointer + +Variabel bertipe pointer ditandai dengan adanya tanda **asterisk** (`*`) tepat sebelum penulisan tipe data ketika deklarasi. + +```go +var number *int +var name *string +``` + +Nilai default variabel pointer adalah `nil` (kosong). Variabel pointer tidak bisa menampung nilai yang bukan pointer, dan sebaliknya variabel biasa tidak bisa menampung nilai pointer. + +Ada dua hal penting yang perlu diketahui mengenai pointer: + +- Variabel biasa bisa diambil nilai pointernya, caranya dengan menambahkan tanda **ampersand** (`&`) tepat sebelum nama variabel. Metode ini disebut dengan **referencing**. +- Dan sebaliknya, nilai asli variabel pointer juga bisa diambil, dengan cara menambahkan tanda **asterisk** (`*`) tepat sebelum nama variabel. Metode ini disebut dengan **dereferencing**. + +OK, langsung saja kita praktekan. + +```go +var numberA int = 4 +var numberB *int = &numberA + +fmt.Println("numberA (value) :", numberA) // 4 +fmt.Println("numberA (address) :", &numberA) // 0xc20800a220 + +fmt.Println("numberB (value) :", *numberB) // 4 +fmt.Println("numberB (address) :", numberB) // 0xc20800a220 +``` + +Variabel `numberB` dideklarasikan bertipe pointer `int` dengan nilai awal adalah referensi variabel `numberA` (bisa dilihat pada kode `&numberA`). Dengan ini, variabel `numberA` dan `numberB` menampung data dengan referensi alamat memori yang sama. + +![Penggunaan variabel pointer](images/A.22_1_pointer.png) + +Variabel pointer jika di-print akan menghasilkan string alamat memori (dalam notasi heksadesimal), contohnya seperti `numberB` yang diprint menghasilkan `0xc20800a220`. + +Nilai asli sebuah variabel pointer bisa didapatkan dengan cara di-dereference terlebih dahulu (bisa dilihat pada kode `*numberB`). + +## A.22.2. Efek Perubahan Nilai Pointer + +Ketika salah satu variabel pointer di ubah nilainya, sedang ada variabel lain yang memiliki referensi memori yang sama, maka nilai variabel lain tersebut juga akan berubah. + +```go +var numberA int = 4 +var numberB *int = &numberA + +fmt.Println("numberA (value) :", numberA) +fmt.Println("numberA (address) :", &numberA) +fmt.Println("numberB (value) :", *numberB) +fmt.Println("numberB (address) :", numberB) + +fmt.Println("") + +numberA = 5 + +fmt.Println("numberA (value) :", numberA) +fmt.Println("numberA (address) :", &numberA) +fmt.Println("numberB (value) :", *numberB) +fmt.Println("numberB (address) :", numberB) +``` + +Variabel `numberA` dan `numberB` memiliki referensi memori yang sama. Perubahan pada salah satu nilai variabel tersebut akan memberikan efek pada variabel lainnya. Pada contoh di atas, `numberA` nilainya di ubah menjadi `5`. membuat nilai asli variabel `numberB` ikut berubah menjadi `5`. + +![Variabel pointer diubah nilainya](images/A.22_2_pointer_change.png) + +## A.22.3. Parameter Pointer + +Parameter bisa juga didesain sebagai pointer. Cara penerapannya kurang lebih sama, dengan cara mendeklarasikan parameter sebagai pointer. + +```go +package main + +import "fmt" + +func main() { + var number = 4 + fmt.Println("before :", number) // 4 + + change(&number, 10) + fmt.Println("after :", number) // 10 +} + +func change(original *int, value int) { + *original = value +} +``` + +Fungsi `change()` memiliki 2 parameter, yaitu `original` yang tipenya adalah pointer `int`, dan `value` yang bertipe `int`. Di dalam fungsi tersebut nilai asli parameter pointer `original` diubah. + +Fungsi `change()` kemudian diimplementasikan di `main`. Variabel `number` yang nilai awalnya adalah `4` diambil referensi-nya lalu digunakan sebagai parameter pada pemanggilan fungsi `change()`. + +Nilai variabel `number` berubah menjadi `10` karena perubahan yang terjadi di dalam fungsi `change` adalah pada variabel pointer. + +![Fungsi berparameter pointer](images/A.22_3_pointer_parameter.png) + +--- + + diff --git a/23-struct.md b/23-struct.md new file mode 100644 index 000000000..be9c3260b --- /dev/null +++ b/23-struct.md @@ -0,0 +1,415 @@ +# A.23. Struct + +Go tidak memiliki class yang ada di bahasa-bahasa strict OOP lain. Tapi Go memiliki tipe data struktur yang disebut dengan Struct. + +Struct adalah kumpulan definisi variabel (atau property) dan atau fungsi (atau method), yang dibungkus sebagai tipe data baru dengan nama tertentu. Property dalam struct, tipe datanya bisa bervariasi. Mirip seperti `map`, hanya saja key-nya sudah didefinisikan di awal, dan tipe data tiap itemnya bisa berbeda. + +Dari sebuah struct, kita bisa buat variabel baru, yang memiliki atribut sesuai skema struct tersebut. Kita sepakati dalam buku ini, variabel tersebut dipanggil dengan istilah **object** atau **object struct**. + +> Konsep struct di golang mirip dengan konsep **class** pada OOP, meski sebenarnya berbeda. Disini penulis menggunakan konsep OOP sebagai analogi, dengan tujuan untuk mempermudah dalam mencerna isi bab ini. + +Dengan memanfaatkan struct, grouping data akan lebih mudah, selain itu dan rapi dan gampang untuk di-maintain. + +## A.23.1. Deklarasi Struct + +Keyword `type` digunakan untuk deklarasi struct. Di bawah ini merupakan contoh cara penggunaannya. + +```go +type student struct { + name string + grade int +} +``` + +Struct `student` dideklarasikan memiliki 2 property, yaitu `name` dan `grade`. Objek yang dibuat dengan struct ini nantinya memiliki skema atau struktur yang sama. + +## A.23.2. Penerapan Struct + +Struct `student` yang sudah disiapkan di atas akan kita manfaatkan untuk membuat variabel objek. Property variabel tersebut di-isi kemudian ditampilkan. + +```go +func main() { + var s1 student + s1.name = "john wick" + s1.grade = 2 + + fmt.Println("name :", s1.name) + fmt.Println("grade :", s1.grade) +} +``` + +Cara membuat variabel objek sama seperti pembuatan variabel biasa. Tinggal tulis saja nama variabel diikuti nama struct, contoh: `var s1 student`. + +Semua property variabel objek pada awalnya memiliki zero value sesuai tipe datanya. + +Property variabel objek bisa diakses nilainya menggunakan notasi titik, contohnya `s1.name`. Nilai property-nya juga bisa diubah, contohnya `s1.grade = 2`. + +![Pengaksesan property variabel objek](images/A.23_1_struct.png) + +## A.23.3. Inisialisasi Object Struct + +Cara inisialisasi variabel objek adalah dengan menambahkan kurung kurawal setelah nama struct. Nilai masing-masing property bisa diisi pada saat inisialisasi. + +Pada contoh berikut, terdapat 3 buah variabel objek yang dideklarasikan dengan cara berbeda. + +```go +var s1 = student{} +s1.name = "wick" +s1.grade = 2 + +var s2 = student{"ethan", 2} + +var s3 = student{name: "jason"} + +fmt.Println("student 1 :", s1.name) +fmt.Println("student 2 :", s2.name) +fmt.Println("student 3 :", s3.name) +``` + +Pada kode di atas, variabel `s1` menampung objek cetakan `student`. Vartiabel tersebut kemudian di-set nilai property-nya. + +Variabel objek `s2` dideklarasikan dengan metode yang sama dengan `s1`, pembedanya di `s2` nilai propertinya di isi langsung ketika deklarasi. Nilai pertama akan menjadi nilai property pertama (yaitu `name`), dan selanjutnya berurutan. + +Pada deklarasi `s3`, dilakukan juga pengisian property ketika pencetakan objek. Hanya saja, yang diisi hanya `name` saja. Cara ini cukup efektif jika digunakan untuk membuat objek baru yang nilai property-nya tidak semua harus disiapkan di awal. Keistimewaan lain menggunakan cara ini adalah penentuan nilai property bisa dilakukan dengan tidak berurutan. Contohnya: + +```go +var s4 = student{name: "wayne", grade: 2} +var s5 = student{grade: 2, name: "bruce"} +``` + +## A.23.4. Variabel Objek Pointer + +Objek yang dibuat dari tipe struct bisa diambil nilai pointer-nya, dan bisa disimpan pada variabel objek yang bertipe struct pointer. Contoh penerapannya: + +```go +var s1 = student{name: "wick", grade: 2} + +var s2 *student = &s1 +fmt.Println("student 1, name :", s1.name) +fmt.Println("student 4, name :", s2.name) + +s2.name = "ethan" +fmt.Println("student 1, name :", s1.name) +fmt.Println("student 4, name :", s2.name) +``` + +`s2` adalah variabel pointer hasil cetakan struct `student`. `s2` menampung nilai referensi `s1`, menjadikan setiap perubahan pada property variabel tersebut, akan juga berpengaruh pada variabel objek `s1`. + +Meskipun `s2` bukan variabel asli, property nya tetap bisa diakses seperti biasa. Inilah keistimewaan property dalam objek pointer, tanpa perlu di-dereferensi nilai asli property tetap bisa diakses. Pengisian nilai pada property tersebut juga bisa langsung menggunakan nilai asli, contohnya seperti `s2.name = "ethan"`. + +![Variabel objek pointer](images/A.23_2_pointer_object.png) + +## A.23.5. Embedded Struct + +**Embedded** struct adalah mekanisme untuk menempelkan sebuah struct sebagai properti struct lain. Agar lebih mudah dipahami, mari kita bahas kode berikut. + +```go +package main + +import "fmt" + +type person struct { + name string + age int +} + +type student struct { + grade int + person +} + +func main() { + var s1 = student{} + s1.name = "wick" + s1.age = 21 + s1.grade = 2 + + fmt.Println("name :", s1.name) + fmt.Println("age :", s1.age) + fmt.Println("age :", s1.person.age) + fmt.Println("grade :", s1.grade) +} + +``` + +Pada kode di atas, disiapkan struct `person` dengan properti yang tersedia adalah `name` dan `age`. Disiapkan juga struct `student` dengan property `grade`. Struct `person` di-embed kedalam struct `student`. Caranya cukup mudah, yaitu dengan menuliskan nama struct yang ingin di-embed ke dalam body `struct` target. + +Embedded struct adalah **mutable**, nilai property-nya nya bisa diubah. + +Khusus untuk properti yang bukan properti asli (properti turunan dari struct lain), bisa diakses dengan cara mengakses struct *parent*-nya terlebih dahulu, contohnya `s1.person.age`. Nilai yang dikembalikan memiliki referensi yang sama dengan `s1.age`. + +## A.23.6. Embedded Struct Dengan Nama Property Yang Sama + +Jika salah satu nama properti sebuah struct memiliki kesamaan dengan properti milik struct lain yang di-embed, maka pengaksesan property-nya harus dilakukan secara explisit atau jelas. Contoh bisa dilihat di kode berikut. + +```go +package main + +import "fmt" + +type person struct { + name string + age int +} + +type student struct { + person + age int + grade int +} + +func main() { + var s1 = student{} + s1.name = "wick" + s1.age = 21 // age of student + s1.person.age = 22 // age of person + + fmt.Println(s1.name) + fmt.Println(s1.age) + fmt.Println(s1.person.age) +} +``` + +Struct `person` di-embed ke dalam struct `student`, dan kedua struct tersebut kebetulan salah satu nama property-nya ada yg sama, yaitu `age`. Cara mengakses property `age` milik struct `person` lewat objek struct `student`, adalah dengan menuliskan nama struct yg di-embed kemudian nama property-nya, contohnya: `s1.person.age = 22`. + +## A.23.7. Pengisian Nilai Sub-Struct + +Pengisian nilai property sub-struct bisa dilakukan dengan langsung memasukkan variabel objek yang tercetak dari struct yang sama. + +```go +var p1 = person{name: "wick", age: 21} +var s1 = student{person: p1, grade: 2} + +fmt.Println("name :", s1.name) +fmt.Println("age :", s1.age) +fmt.Println("grade :", s1.grade) +``` + +Pada deklarasi `s1`, property `person` diisi variabel objek `p1`. + +## A.23.8. Anonymous Struct + +Anonymous struct adalah struct yang tidak dideklarasikan di awal sebagai tipe data baru, melainkan langsung ketika pembuatan objek. Teknik ini cukup efisien untuk pembuatan variabel objek yang struct-nya hanya dipakai sekali. + +```go +package main + +import "fmt" + +type person struct { + name string + age int +} + +func main() { + var s1 = struct { + person + grade int + }{} + s1.person = person{"wick", 21} + s1.grade = 2 + + fmt.Println("name :", s1.person.name) + fmt.Println("age :", s1.person.age) + fmt.Println("grade :", s1.grade) +} +``` + +Pada kode di atas, variabel `s1` langsung diisi objek anonymous struct yang memiliki property `grade`, dan property `person` yang merupakan embedded struct. + +Salah satu aturan yang perlu diingat dalam pembuatan anonymous struct adalah, deklarasi harus diikuti dengan inisialisasi. Bisa dilihat pada `s1` setelah deklarasi struktur struct, terdapat kurung kurawal untuk inisialisasi objek. Meskipun nilai tidak diisikan di awal, kurung kurawal tetap harus ditulis. + +```go +// anonymous struct tanpa pengisian property +var s1 = struct { + person + grade int +}{} + +// anonymous struct dengan pengisian property +var s2 = struct { + person + grade int +}{ + person: person{"wick", 21}, + grade: 2, +} +``` + +## A.23.9. Kombinasi Slice & Struct + +Slice dan `struct` bisa dikombinasikan seperti pada slice dan `map`, caranya penggunaannya-pun mirip, cukup tambahkan tanda `[]` sebelum tipe data pada saat deklarasi. + +```go +type person struct { + name string + age int +} + +var allStudents = []person{ + {name: "Wick", age: 23}, + {name: "Ethan", age: 23}, + {name: "Bourne", age: 22}, +} + +for _, student := range allStudents { + fmt.Println(student.name, "age is", student.age) +} +``` + +## A.23.10. Inisialisasi Slice Anonymous Struct + +Anonymous struct bisa dijadikan sebagai tipe sebuah slice. Dan nilai awalnya juga bisa diinisialisasi langsung pada saat deklarasi. Berikut adalah contohnya: + +```go +var allStudents = []struct { + person + grade int +}{ + {person: person{"wick", 21}, grade: 2}, + {person: person{"ethan", 22}, grade: 3}, + {person: person{"bond", 21}, grade: 3}, +} + +for _, student := range allStudents { + fmt.Println(student) +} +``` + +## A.23.11. Deklarasi Anonymous Struct Menggunakan Keyword **var** + +Cara lain untuk deklarasi anonymous struct adalah dengan menggunakan keyword `var`. + +```go +var student struct { + person + grade int +} + +student.person = person{"wick", 21} +student.grade = 2 +``` + +Statement `type student struct` adalah contoh cara deklarasi struct. Maknanya akan berbeda ketika keyword `type` diganti `var`, seperti pada contoh di atas `var student struct`, yang artinya dicetak sebuah objek dari anonymous struct kemudian disimpan pada variabel bernama `student`. + +Deklarasi anonymous struct menggunakan metode ini juga bisa dilakukan beserta inisialisasi-nya. + +```go +// hanya deklarasi +var student struct { + grade int +} + +// deklarasi sekaligus inisialisasi +var student = struct { + grade int +} { + 12, +} +``` + +## A.23.12. Nested struct + +Nested struct adalah anonymous struct yang di-embed ke sebuah struct. Deklarasinya langsung didalam struct peng-embed. Contoh: + +```go +type student struct { + person struct { + name string + age int + } + grade int + hobbies []string +} +``` + +Teknik ini biasa digunakan ketika decoding data **json** yang struktur datanya cukup kompleks dengan proses decode hanya sekali. + +## A.23.13. Deklarasi Dan Inisialisasi Struct Secara Horizontal + +Deklarasi struct bisa dituliskan secara horizontal, caranya bisa dilihat pada kode berikut: + +```go +type person struct { name string; age int; hobbies []string } +``` + +Tanda semi-colon (`;`) digunakan sebagai pembatas deklarasi poperty yang dituliskan secara horizontal. Inisialisasi nilai juga bisa dituliskan dengan metode ini. Contohnya: + +```go +var p1 = struct { name string; age int } { age: 22, name: "wick" } +var p2 = struct { name string; age int } { "ethan", 23 } +``` + +Bagi pengguna editor Sublime yang terinstal plugin GoSublime didalamnya, cara ini tidak akan bisa dilakukan, karena setiap kali file di-save, kode program dirapikan. Jadi untuk mengetesnya bisa dengan menggunakan editor lain. + +## A.23.14. Tag property dalam struct + +Tag merupakan informasi opsional yang bisa ditambahkan pada masing-masing property struct. + +```go +type person struct { + name string `tag1` + age int `tag2` +} +``` + +Tag biasa dimanfaatkan untuk keperluan encode/decode data json. Informasi tag juga bisa diakses lewat reflect. Nantinya akan ada pembahasan yang lebih detail mengenai pemanfaatan tag dalam struct, terutama ketika sudah masuk bab JSON. + +## A.23.15. Type Alias + +Sebuah tipe data, seperti struct, bisa dibuatkan alias baru, caranya dengan `type NamaAlias = TargetStruct`. Contoh: + +```go +type Person struct { + name string + age int +} +type People = Person + +var p1 = Person{"wick", 21} +fmt.Println(p1) + +var p2 = People{"wick", 21} +fmt.Println(p2) +``` + +Pada kode di atas, sebuah alias bernama `People` dibuat untuk struct `Person`. + +Casting dari objek (yang dicetak lewat struct tertentu) ke tipe yang merupakan alias dari struct pencetak, hasilnya selalu valid. Berlaku juga sebaliknya. + +```go +people := People{"wick", 21} +fmt.Println(Person(people)) + +person := Person{"wick", 21} +fmt.Println(People(person)) +``` + +Pembuatan struct baru juga bisa dilakukan lewat teknik type alias. Silakan perhatikan kode berikut. + +```go +type People1 struct { + name string + age int +} +type People2 = struct { + name string + age int +} +``` + +Struct `People1` dideklarasikan. Struct alias `People2` juga dideklarasikan, struct ini merupakan alias dari anonymous struct. Penggunaan teknik type alias untuk anonymous struct menghasilkan output yang ekuivalen dengan pendeklarasian struct. + +Perlu diketahui juga, dan di atas sudah sempat disinggung, bahwa teknik type alias ini tidak didesain hanya untuk pembuatan alias pada tipe struct saja, semua jenis tipe data bisa dibuatkan alias. Di contoh berikut, dipersiapkan tipe `Number` yang merupakan alias dari tipe data `int`. + +```go +type Number = int +var num Number = 12 +``` + +--- + + diff --git a/24-method.md b/24-method.md new file mode 100644 index 000000000..188f72e8b --- /dev/null +++ b/24-method.md @@ -0,0 +1,155 @@ +# A.24. Method + +**Method** adalah fungsi yang menempel pada `type` (bisa `struct` atau tipe data lainnya). Method bisa diakses lewat variabel objek. + +Keunggulan method dibanding fungsi biasa adalah memiliki akses ke property struct hingga level *private* (level akses nantinya akan dibahas lebih detail pada bab selanjutnya). Dan juga, dengan menggunakan method sebuah proses bisa di-enkapsulasi dengan baik. + +## A.24.1. Penerapan Method + +Cara menerapkan method sedikit berbeda dibanding penggunaan fungsi. Ketika deklarasi, ditentukan juga siapa pemilik method tersebut. Contohnya bisa dilihat pada kode berikut: + +```go +package main + +import "fmt" +import "strings" + +type student struct { + name string + grade int +} + +func (s student) sayHello() { + fmt.Println("halo", s.name) +} + +func (s student) getNameAt(i int) string { + return strings.Split(s.name, " ")[i-1] +} +``` + +Cara deklarasi method sama seperti fungsi, hanya saja perlu ditambahkan deklarasi variabel objek di sela-sela keyword `func` dan nama fungsi. Struct yang digunakan akan menjadi pemilik method. + +`func (s student) sayHello()` maksudnya adalah fungsi `sayHello` dideklarasikan sebagai method milik struct `student`. Pada contoh di atas struct `student` memiliki dua buah method, yaitu `sayHello()` dan `getNameAt()`. + +Contoh pemanfaatan method bisa dilihat pada kode berikut. + +```go +func main() { + var s1 = student{"john wick", 21} + s1.sayHello() + + var name = s1.getNameAt(2) + fmt.Println("nama panggilan :", name) +} +``` + +Output: + +![Penggunaan method](images/A.24_1_method.png) + +Cara mengakses method sama seperti pengaksesan properti berupa variabel. Tinggal panggil saja methodnya. + +```go +s1.sayHello() +var name = s1.getNameAt(2) +``` + +Method memiliki sifat yang sama persis dengan fungsi biasa. Seperti bisa berparameter, memiliki nilai balik, dan lainnya. Dari segi sintaks, pembedanya hanya ketika pengaksesan dan deklarasi. Bisa dilihat di kode berikut, sekilas perbandingan penulisan fungsi dan method. + +```go +func sayHello() { +func (s student) sayHello() { + +func getNameAt(i int) string { +func (s student) getNameAt(i int) string { +``` + +## A.24.2. Method Pointer + +Method pointer adalah method yang variabel objek pemilik method tersebut berupa pointer. + +Kelebihan method jenis ini adalah, ketika kita melakukan manipulasi nilai pada property lain yang masih satu struct, nilai pada property tersebut akan di rubah pada reference nya. Lebih jelasnya perhatikan kode berikut. + +```go +package main + +import "fmt" + +type student struct { + name string + grade int +} + +func (s student) changeName1(name string) { + fmt.Println("---> on changeName1, name changed to", name) + s.name = name +} + +func (s *student) changeName2(name string) { + fmt.Println("---> on changeName2, name changed to", name) + s.name = name +} + +func main() { + var s1 = student{"john wick", 21} + fmt.Println("s1 before", s1.name) + // john wick + + s1.changeName1("jason bourne") + fmt.Println("s1 after changeName1", s1.name) + // john wick + + s1.changeName2("ethan hunt") + fmt.Println("s1 after changeName2", s1.name) + // ethan hunt +} +``` + +Output: + +![Penggunaan method pointer](images/A.24_2_method_pointer.png) + +Setelah eksekusi statement `s1.changeName1("jason bourne")`, nilai `s1.name` tidak berubah. Sebenarnya nilainya berubah tapi hanya dalam method `changeName1()` saja, nilai pada reference di objek-nya tidak berubah. Karena itulah ketika objek di print value dari `s1.name` tidak berubah. + +Keistimewaan lain method pointer adalah, method itu sendiri bisa dipanggil dari objek pointer maupun objek biasa. + +```go +// pengaksesan method dari variabel objek biasa +var s1 = student{"john wick", 21} +s1.sayHello() + +// pengaksesan method dari variabel objek pointer +var s2 = &student{"ethan hunt", 22} +s2.sayHello() +``` + +--- + +Berikut adalah penjelasan tambahan mengenai beberapa hal pada bab ini. + +### A.24.2.1. Penggunaan Fungsi `strings.Split()` + +Di bab ini ada fungsi baru yang kita gunakan: `strings.Split()`. Fungsi ini berguna untuk memisah string menggunakan pemisah yang ditentukan sendiri. Hasilnya adalah array berisikan kumpulan substring. + +```go +strings.Split("ethan hunt", " ") +// ["ethan", "hunt"] +``` + +Pada contoh di atas, string `"ethan hunt"` dipisah menggunakan separator spasi `" "`. Maka hasilnya terbentuk array berisikan 2 data, `"ethan"` dan `"hunt"`. + +## A.24.3. Apakah `fmt.Println()` & `strings.Split()` Juga Merupakan Method? + +Setelah tahu apa itu method dan bagaimana penggunaannya, mungkin akan muncul di benak kita bahwa kode seperti `fmt.Println()`, `strings.Split()` dan lainnya-yang-berada-pada-package-lain adalah merupakan method. Tapi sayangnya **bukan**. `fmt` disitu bukanlah variabel objek, dan `Println()` bukan merupakan method-nya. + +`fmt` adalah nama **package** yang di-import (bisa dilihat pada kode `import "fmt"`). Sedangkan `Println()` adalah **nama fungsi**. Untuk mengakses fungsi yang berada pada package lain, harus dituliskan nama package-nya. Hal ini berlaku juga di dalam package `main`. Jika ada fungsi dalam package main yang diakses dari package lain yang berbeda, maka penulisannya `main.NamaFungsi()`. + +Lebih detailnya akan dibahas di bab selanjutnya. + +--- + + diff --git a/25-properti-public-dan-private.md b/25-properti-public-dan-private.md new file mode 100644 index 000000000..1fb8752fb --- /dev/null +++ b/25-properti-public-dan-private.md @@ -0,0 +1,330 @@ +# A.25. Property Public Dan Private + +Bab ini membahas mengenai *modifier* public dan private dalam Go. Kapan sebuah struct, fungsi, atau method bisa diakses dari package lain dan kapan tidak. + +## A.25.0. PERINGATAN + +Peringatan ini ditulis karena sudah terlalu banyak email yang penulis dapati, perihal error yang muncul ketika mempraktekan beberapa kode pada bab ini. + +Bab ini memiliki beberapa perbedaan dibanding lainnya. Jika pembaca mengikuti secara berurutan, membaca penjelasan dan pembahasan yang sudah tertulis, maka **pasti akan mendapati 3 buah error**. Di tiap-tiap error, sebenarnya sudah terlampir: + +1. Screenshot error +2. Penjelasan penyebab error +3. Cara resolve atau solusi dari ketiga error tersebut. + +Kesimpulan dari email-email yang penulis dapati: **pembaca bingung karena mendapati error, dan tidak tau cara mengatasi error tersebut. Padahal sudah ada keterangan yang jelas bahwa error tersebut pasti muncul, dan juga sudah dijelaskan cara mengatasinya. Ini kemungkinan besar disebabkan karena pembaca hanya copy-paste source code, tanpa membaca penjelasan-penjelasan yang padahal sudah tertulis cukup mendetail**. + +> Oleh karena itu, JANGAN CUMA COPAS SOURCE KODE, BACA, PELAJARI, DAN PAHAMI! *No hard feeling* 👌😁 + +## A.25.1. Package Public & Private + +Pengembangan aplikasi dalam *real development* pasti membutuhkan banyak sekali file program. Tidak mungkin dalam satu project semua file memiliki nama package `main`, biasanya akan dipisah sebagai package berbeda sesuai bagiannya. + +Project folder selain berisikan file-file `.go` juga bisa berisikan folder. Di bahasa Go, setiap satu folder atau subfolder adalah satu package, file-file yang ada didalamnya harus memiliki nama package yang berbeda dengan file di folder lain. + +Dalam sebuah package, biasanya kita menulis sangat banyak komponen, entah itu fungsi, struct, variabel, atau lainnya. Komponen tersebut bisa leluasa digunakan dalam package yang sama. Contoh sederhananya seperti program yang telah kita praktekan di bab sebelum-sebelumnya, dalam package `main` ada banyak yang di-define: fungsi, variabel, closure, struct, dan lainnya; kesemuanya bisa langsung dimanfaatkan. + +Jika dalam satu program terdapat lebih dari 1 package, atau ada package lain selain `main`, tidak bisa komponen dalam package lain tersebut diakses secara bebas dari `main`, karena tiap komponen memiliki hak akses. + +Ada 2 jenis hak akses: + + - Akses Public, menandakan komponen tersebut diperbolehkan untuk diakses dari package lain yang berbeda + - Akses Private, berarti komponen hanya bisa diakses dalam package yang sama, bisa dalam satu file saja atau dalam beberapa file yang masih 1 folder. + +Penentuan hak akses yang tepat untuk tiap komponen sangatlah penting. + +Di Go cara menentukan level akses atau modifier sangat mudah, penandanya adalah **character case** karakter pertama nama fungsi, struct, variabel, atau lainnya. Ketika namanya diawali dengan huruf kapital menandakan bahwa modifier-nya public. Dan sebaliknya, jika diawali huruf kecil, berarti private. + +## A.25.2. Penggunaan Package, Import, Dan Modifier Public & Private + +Agar lebih mudah dipahami, maka langsung saja kita praktekan. + +Pertama buat folder proyek baru bernama `belajar-golang-level-akses` di dalam folder `$GOPATH/src`, didalamnya buat file baru bernama `main.go`, set nama package file tersebut sebagai **main**. + +Selanjutnya buat folder baru bernama `library` di dalam folder baru yang sudah dibuat tadi. Didalamnya buat file baru `library.go`, set nama package-nya **library**. + +![Struktur folder dan file](images/A.25_1_folder_structure.png) + +Buka file `library.go` lalu isi dengan kode berikut. + +```go +package library + +import "fmt" + +func SayHello() { + fmt.Println("hello") +} + +func introduce(name string) { + fmt.Println("nama saya", name) +} +``` + +File `library.go` yang telah dibuat ditentukan nama package-nya adalah `library` (sesuai dengan nama folder), berisi dua buah fungsi, `SayHello()` dan `introduce()`. + + - Fungsi `SayHello()`, level aksesnya adalah publik, ditandai dengan nama fungsi diawali huruf besar. + - Fungsi `introduce()` dengan level akses private, ditandai oleh huruf kecil di awal nama fungsi. + +Selanjutnya kita lakukan tes apakah memang fungsi yang ber-modifier private dalam package `library` tidak bisa diakses dari package lain. + +Buka file `main.go`, lalu tulis kode berikut. + +```go +package main + +import "belajar-golang-level-akses/library" + +func main() { + library.SayHello() + library.introduce("ethan") +} +``` + +Bisa dilihat bahwa package `library` yang telah dibuat tadi, di-import ke dalam package `main`. + +Folder utama atau root folder dalam project yang sedang digarap adalah `belajar-golang-level-akses`, sehingga untuk import package lain yang merupakan subfolder, harus dituliskan lengkap path folder nya, seperti `"belajar-golang-level-akses/library"`. + +Penanda root folder adalah, file di dalam folder tersebut package-nya `main`, dan file tersebut dijalankan menggunakan `go run`. + +Perhatikan kode berikut. + +```go +library.SayHello() +library.introduce("ethan") +``` + +Cara pemanggilan fungsi yang berada dalam package lain adalah dengan menuliskan nama package target diikut dengan nama fungsi menggunakan *dot notation* atau tanda titik, seperti `library.SayHello()` atau `library.introduce("ethan")` + +OK, sekarang coba jalankan kode yang sudah disiapkan di atas, hasilnya error. + +![Error saat menjalankan program](images/A.25_2_error.png) + +Error di atas disebabkan karena fungsi `introduce()` yang berada dalam package `library` memiliki level akses **private**, fungsi ini tidak bisa diakses dari package lain (pada kasus ini `main`). Agar bisa diakses, solusinya bisa dengan menjadikannya public, atau diubah cara pemanggilannya. Disini kita menggunakan cara ke-2. + +Tambahkan parameter `name` pada fungsi `SayHello()`, lalu panggil fungsi `introduce()` dengan menyisipkan parameter `name` dari dalam fungsi `SayHello()`. + +```go +func SayHello(name string) { + fmt.Println("hello") + introduce(name) +} +``` + +Di `main`, cukup panggil fungsi `SayHello()` saja, sisipkan sebuah string sebagai parameter. + +```go +func main() { + library.SayHello("ethan") +} +``` + +Coba jalankan lagi. + +![Contoh penerapan pemanggilan fungsi dari package berbeda](images/A.25_2_success.png) + +## A.25.3. Penggunaan Public & Private Pada Struct Dan Propertinya + +Level akses private dan public juga bisa diterapkan di fungsi, struct, method, maupun property variabel. Cara penggunaannya sama seperti pada pembahasan sebelumnya, yaitu dengan menentukan **character case** huruf pertama nama komponen, apakah huruf besar atau kecil. + +Belajar tentang level akses di Go akan lebih cepat jika langsung praktek. Oleh karena itu langsung saja. Hapus isi file `library.go`, buat struct baru dengan nama `student` didalamnya. + +```go +package library + +type student struct { + Name string + grade int +} +``` + +Buat contoh sederhana penerapan struct di atas pada file `main.go`. + +```go +package main + +import "belajar-golang-level-akses/library" +import "fmt" + +func main() { + var s1 = library.student{"ethan", 21} + fmt.Println("name ", s1.Name) + fmt.Println("grade", s1.grade) +} +``` + +Setelah itu jalankan program. + +![Error saat menjalankan program](images/A.25_3_error.png) + +Error muncul lagi, kali ini penyebabnya adalah karena struct `student` masih di set sebagai private. Ganti menjadi public (dengan cara mengubah huruf awalnya menjadi huruf besar) lalu jalankan. + +```go +// pada library/library.go +type Student struct { + Name string + grade int +} + +// pada main.go +var s1 = library.student{"ethan", 21} +fmt.Println("name ", s1.Name) +fmt.Println("grade", s1.grade) +``` + +Output: + +![Error lain muncul saat menjalankan program](images/A.25_4_error.png) + +Error masih tetap muncul, tapi kali ini berbeda. Error yang baru ini disebabkan karena salah satu properti dari struct `Student` bermodifier private. Properti yg dimaksud adalah `grade`. Ubah menjadi public, lalu jalankan lagi. + +```go +// pada library/library.go +type Student struct { + Name string + Grade int +} + +// pada main.go +var s1 = library.Student{"ethan", 21} +fmt.Println("name ", s1.Name) +fmt.Println("grade", s1.Grade) +``` + +Dari contoh program di atas, bisa disimpulkan bahwa untuk menggunakan `struct` yang berada di package lain, selain nama stuct-nya harus bermodifier public, properti yang diakses juga harus public. + +![Contoh penerapan pemanfaatan struct dan propertynya dari package berbeda](images/A.25_4_success.png) + +## A.25.4. Import Dengan Prefix Tanda Titik + +Seperti yang kita tahu, untuk mengakses fungsi/struct/variabel yg berada di package lain, nama package nya perlu ditulis, contohnya seperti pada penggunaan penggunaan `library.Student` dan `fmt.Println()`. + +Di Go, komponen yang berada di package lain yang di-import bisa dijadikan se-level dengan komponen package peng-import, caranya dengan menambahkan tanda titik (`.`) setelah penulisan keyword `import`. Maksud dari se-level disini adalah, semua properti di package lain yg di-import bisa diakses tanpa perlu menuliskan nama package, seperti ketika mengakses sesuatu dari file yang sama. + +```go +import ( + . "belajar-golang-level-akses/library" + "fmt" +) + +func main() { + var s1 = Student{"ethan", 21} + fmt.Println("name ", s1.Name) + fmt.Println("grade", s1.Grade) +} +``` + +Pada kode di atas package `library` di-import menggunakan tanda titik. Dengan itu, pemanggilan struct `Student` tidak perlu dengan menuliskan nama package nya. + +## 25.5. Pemanfaatan Alias Ketika Import Package + +Fungsi yang berada di package lain bisa diakses dengan cara menuliskan nama-package diikuti nama fungsi-nya, contohnya seperti `fmt.Println()`. Package yang sudah di-import tersebut bisa diubah namanya dengan cara menggunakan alias pada saat import. Contohnya bisa dilihat pada kode berikut. + +```go +import ( + f "fmt" +) + +func main() { + f.Println("Hello World!") +} +``` + +Pada kode di-atas, package `fmt` di tentukan aliasnya adalah `f`, untuk mengakses `Println()` cukup dengan `f.Println()`. + +## A.25.6. Mengakses Properti Dalam File Yang Package-nya Sama + +Jika properti yang ingin di akses masih dalam satu package tapi berbeda file, cara mengaksesnya bisa langsung dengan memanggil namanya. Hanya saja ketika eksekusi, file-file lain yang yang nama package-nya sama juga ikut dipanggil. + +Langsung saja kita praktekan, buat file baru dalam `belajar-golang-level-akses` dengan nama `partial.go`. + +![File `partial.go` disiapkan setara dengan file `main.go`](images/A.25_5_structure.png) + +Tulis kode berikut pada file `partial.go`. File ini kita set package-nya **main** (sama dengan nama package file `main.go`). + +```go +package main + +import "fmt" + +func sayHello(name string) { + fmt.Println("halo", name) +} +``` + +Hapus semua isi file `main.go`, lalu silakan tulis kode berikut. + +```go +package main + +func main() { + sayHello("ethan") +} +``` + +Sekarang terdapat 2 file berbeda (`main.go` dan `partial.go`) dengan package adalah sama, `main`. Pada saat **go build** atau **go run**, semua file dengan nama package `main` harus dituliskan sebagai argumen command. + +``` +$ go run main.go partial.go +``` + +Fungsi `sayHello` pada file `partial.go` bisa dikenali meski level aksesnya adalah private. Hal ini karena kedua file tersebut (`main.go` dan `partial.go`) memiliki package yang sama. + +> Cara lain untuk menjalankan program bisa dengan perintah `go run *.go`, dengan cara ini tidak perlu menuliskan nama file nya satu per satu. + +![Pemanggilan fungsi private dari dalam package yang sama](images/A.25_6_multi_main.png) + +## A.25.6.1. Fungsi `init()` + +Selain fungsi `main()`, terdapat juga fungsi spesial, yaitu `init()`. Fungsi ini otomatis dipanggil pertama kali ketika aplikasi di-run. Jika fungsi ini berada dalam package main, maka dipanggil lebih dulu sebelum fungsi `main()` dieksekusi. + +Langsung saja kita praktekkan. Buka file `library.go`, hapus isinya lalu isi dengan kode berikut. + +```go +package library + +import "fmt" + +var Student = struct { + Name string + Grade int +}{} + +func init() { + Student.Name = "John Wick" + Student.Grade = 2 + + fmt.Println("--> library/library.go imported") +} +``` + +Pada package tersebut, variabel `Student` dibuat dengan isi anonymous struct. Dalam fungsi init, nilai `Name` dan `Grade` variabel di-set. + +Selanjutnya buka file `main.go`, isi dengan kode berikut. + +```go +package main + +import "belajar-golang-level-akses/library" +import "fmt" + +func main() { + fmt.Printf("Name : %s\n", library.Student.Name) + fmt.Printf("Grade : %d\n", library.Student.Grade) +} +``` + +Package `library` di-import, dan variabel `Student` dikonsumsi. Pada saat import package, fungsi `init()` yang berada didalamnya langsung dieksekusi. + +Property variabel objek `Student` akan diisi dan sebuah pesan ditampilkan ke console. + +Dalam sebuah package diperbolehkan ada banyak fungsi `init()` (urutan eksekusinya adalah sesuai file mana yg terlebih dahulu digunakan). Fungsi ini dipanggil sebelum fungsi `main()`, pada saat eksekusi program. + +![Fungsi `init()`](images/A.25_7_init.png) + +--- + + diff --git a/26-interface.md b/26-interface.md new file mode 100644 index 000000000..f2ed1ad01 --- /dev/null +++ b/26-interface.md @@ -0,0 +1,182 @@ +# A.26. Interface + +Interface adalah kumpulan definisi method yang tidak memiliki isi (hanya definisi saja), yang dibungkus dengan nama tertentu. + +Interface merupakan tipe data. Nilai objek bertipe interface zero value-nya adalah `nil`. Interface mulai bisa digunakan jika sudah ada isinya, yaitu objek konkret yang memiliki definisi method minimal sama dengan yang ada di interface-nya. + +## A.26.1. Penerapan Interface + +Yang pertama perlu dilakukan untuk menerapkan interface adalah menyiapkan interface beserta definisi method nya. Keyword `type` dan `interface` digunakan untuk pendefinisian interface. + +```go +package main + +import "fmt" +import "math" + +type hitung interface { + luas() float64 + keliling() float64 +} +``` + +Pada kode di atas, interface `hitung` memiliki 2 definisi method, `luas()` dan `keliling()`. Interface ini nantinya digunakan sebagai tipe data pada variabel, dimana variabel tersebut akan menampung menampung objek bangun datar hasil dari struct yang akan kita buat. + +Dengan memanfaatkan interface `hitung`, perhitungan luas dan keliling bangun datar bisa dilakukan, tanpa perlu tahu jenis bangun datarnya sendiri itu apa. + +Siapkan struct bangun datar `lingkaran`, struct ini memiliki method yang beberapa diantaranya terdefinisi di interface `hitung`. + +```go +type lingkaran struct { + diameter float64 +} + +func (l lingkaran) jariJari() float64 { + return l.diameter / 2 +} + +func (l lingkaran) luas() float64 { + return math.Pi * math.Pow(l.jariJari(), 2) +} + +func (l lingkaran) keliling() float64 { + return math.Pi * l.diameter +} +``` + +Struct `lingkaran` di atas memiliki tiga method, `jariJari()`, `luas()`, dan `keliling()`. + +Selanjutnya, siapkan struct bangun datar `persegi`. + +```go +type persegi struct { + sisi float64 +} + +func (p persegi) luas() float64 { + return math.Pow(p.sisi, 2) +} + +func (p persegi) keliling() float64 { + return p.sisi * 4 +} +``` + +Perbedaan struct `persegi` dengan `lingkaran` terletak pada method `jariJari()`. Struct `persegi` tidak memiliki method tersebut. Tetapi meski demikian, variabel objek hasil cetakan 2 struct ini akan tetap bisa ditampung oleh variabel cetakan interface `hitung`, karena dua method yang ter-definisi di interface tersebut juga ada pada struct `persegi` dan `lingkaran`, yaitu `luas()` dan `keliling()`. + +Buat implementasi perhitungan di `main`. + +```go +func main() { + var bangunDatar hitung + + bangunDatar = persegi{10.0} + fmt.Println("===== persegi") + fmt.Println("luas :", bangunDatar.luas()) + fmt.Println("keliling :", bangunDatar.keliling()) + + bangunDatar = lingkaran{14.0} + fmt.Println("===== lingkaran") + fmt.Println("luas :", bangunDatar.luas()) + fmt.Println("keliling :", bangunDatar.keliling()) + fmt.Println("jari-jari :", bangunDatar.(lingkaran).jariJari()) +} +``` + +Perhatikan kode di atas. Variabel objek `bangunDatar` bertipe interface `hitung`. Variabel tersebut digunakan untuk menampung objek konkrit buatan struct `lingkaran` dan `persegi`. + +Dari variabel tersebut, method `luas()` dan `keliling()` diakses. Secara otomatis Golang akan mengarahkan pemanggilan method pada interface ke method asli milik struct yang bersangkutan. + +![Pemanfaatan interface](images/A.26_1_interface.png) + +Method `jariJari()` pada struct `lingkaran` tidak akan bisa diakses karena tidak terdefinisi dalam interface `hitung`. Pengaksesannya dengan paksa akan menyebabkan error. + +Untuk mengakses method yang tidak ter-definisi di interface, variabel-nya harus di-casting terlebih dahulu ke tipe asli variabel konkritnya (pada kasus ini tipenya `lingkaran`), setelahnya method akan bisa diakses. + +Cara casting objek interface sedikit unik, yaitu dengan menuliskan nama tipe tujuan dalam kurung, ditempatkan setelah nama interface dengan menggunakan notasi titik (seperti cara mengakses property, hanya saja ada tanda kurung nya). Contohnya bisa dilihat di kode berikut. Statement `bangunDatar.(lingkaran)` adalah contoh casting pada objek interface. + +```go +var bangunDatar hitung = lingkaran{14.0} +var bangunLingkaran lingkaran = bangunDatar.(lingkaran) + +bangunLingkaran.jariJari() +``` + +Perlu diketahui juga, jika ada interface yang menampung objek konkrit dimana struct-nya tidak memiliki salah satu method yang terdefinisi di interface, error juga akan muncul. Intinya kembali ke aturan awal, variabel interface hanya bisa menampung objek yang minimal memiliki semua method yang terdefinisi di interface-nya. + +## A.26.2. Embedded Interface + +Interface bisa di-embed ke interface lain, sama seperti struct. Cara penerapannya juga sama, cukup dengan menuliskan nama interface yang ingin di-embed ke dalam interface tujuan. + +Pada contoh berikut, disiapkan interface bernama `hitung2d` dan `hitung3d`. Kedua interface tersebut kemudian di-embed ke interface baru bernama `hitung`. + +```go +package main + +import "fmt" +import "math" + +type hitung2d interface { + luas() float64 + keliling() float64 +} + +type hitung3d interface { + volume() float64 +} + +type hitung interface { + hitung2d + hitung3d +} +``` + +Interface `hitung2d` berisikan method untuk kalkulasi luas dan keliling, sedang `hitung3d` berisikan method untuk mencari volume bidang. Kedua interface tersebut diturunkan di interface `hitung`, menjadikannya memiliki kemampuan untuk menghitung luas, keliling, dan volume. + +Next, siapkan struct baru bernama `kubus` yang memiliki method `luas()`, `keliling()`, dan `volume()`. + +```go +type kubus struct { + sisi float64 +} + +func (k *kubus) volume() float64 { + return math.Pow(k.sisi, 3) +} + +func (k *kubus) luas() float64 { + return math.Pow(k.sisi, 2) * 6 +} + +func (k *kubus) keliling() float64 { + return k.sisi * 12 +} +``` + +Objek hasil cetakan struct `kubus` di atas, nantinya akan ditampung oleh objek cetakan interface `hitung` yang isinya merupakan gabungan interface `hitung2d` dan `hitung3d`. + +Terakhhir, buat implementasi-nya di main. + +```go +func main() { + var bangunRuang hitung = &kubus{4} + + fmt.Println("===== kubus") + fmt.Println("luas :", bangunRuang.luas()) + fmt.Println("keliling :", bangunRuang.keliling()) + fmt.Println("volume :", bangunRuang.volume()) +} +``` + +Bisa dilihat di kode di atas, lewat interface `hitung`, method `luas`, `keliling`, dan `volume` bisa di akses. + +Pada bab 24 dijelaskan bahwa method pointer bisa diakses lewat variabel objek biasa dan variabel objek pointer. Variabel objek yang dicetak menggunakan struct yang memiliki method pointer, jika ditampung kedalam variabel interface, harus diambil referensi-nya terlebih dahulu. Contohnya bisa dilihat pada kode di atas `var bangunRuang hitung = &kubus{4}`. + +![Embedded interface](images/A.26_2_embedded_interface.png) + +--- + + diff --git a/27-interface-kosong.md b/27-interface-kosong.md new file mode 100644 index 000000000..69d7987d3 --- /dev/null +++ b/27-interface-kosong.md @@ -0,0 +1,139 @@ +# A.27. Interface Kosong + +Interface kosong atau *empty interface* yang dinotasikan dengan `interface{}` merupakan tipe data yang sangat spesial. Variabel bertipe ini bisa menampung segala jenis data, bahkan array, pointer, apapun. Tipe data dengan konsep ini biasa disebut dengan **dynamic typing**. + +## A.27.1. Penggunaan `interface{}` + +`interface{}` merupakan tipe data, sehingga cara penggunaannya sama seperti pada tipe data lainnya, hanya saja nilai yang diisikan bisa apa saja. Contoh: + +```go +package main + +import "fmt" + +func main() { + var secret interface{} + + secret = "ethan hunt" + fmt.Println(secret) + + secret = []string{"apple", "manggo", "banana"} + fmt.Println(secret) + + secret = 12.4 + fmt.Println(secret) +} +``` + +Keyword `interface` seperti yang kita tau, digunakan untuk pembuatan interface. Tetapi ketika ditambahkan kurung kurawal (`{}`) di belakang-nya (menjadi `interface{}`), maka kegunaannya akan berubah, yaitu sebagai tipe data. + +![Segala jenis data bisa ditampung `interface{}`](images/A.27_1_empty_interface.png) + +Agar tidak bingung, coba perhatikan kode berikut. + +```go +var data map[string]interface{} + +data = map[string]interface{}{ + "name": "ethan hunt", + "grade": 2, + "breakfast": []string{"apple", "manggo", "banana"}, +} +``` + +Pada kode di atas, disiapkan variabel `data` dengan tipe `map[string]interface{}`, yaitu sebuah koleksi dengan key bertipe `string` dan nilai bertipe interface kosong `interface{}`. + +Kemudian variabel tersebut di-inisialisasi, ditambahkan lagi kurung kurawal setelah keyword deklarasi untuk kebutuhan pengisian data, `map[string]interface{}{ /* data */ }`. + +Dari situ terlihat bahwa `interface{}` bukanlah sebuah objek, melainkan tipe data. + +## A.27.2. Casting Variabel Interface Kosong + +Variabel bertipe `interface{}` bisa ditampilkan ke layar sebagai `string` dengan memanfaatkan fungsi print, seperti `fmt.Println()`. Tapi perlu diketahui bahwa nilai yang dimunculkan tersebut bukanlah nilai asli, melainkan bentuk string dari nilai aslinya. + +Hal ini penting diketahui, karena untuk melakukan operasi yang membutuhkan nilai asli pada variabel yang bertipe `interface{}`, diperlukan casting ke tipe aslinya. Contoh seperti pada kode berikut. + +```go +package main + +import "fmt" +import "strings" + +func main() { + var secret interface{} + + secret = 2 + var number = secret.(int) * 10 + fmt.Println(secret, "multiplied by 10 is :", number) + + secret = []string{"apple", "manggo", "banana"} + var gruits = strings.Join(secret.([]string), ", ") + fmt.Println(gruits, "is my favorite fruits") +} +``` + +Pertama, variabel `secret` menampung nilai bertipe numerik. Ada kebutuhan untuk mengalikan nilai yang ditampung variabel tersebut dengan angka `10`. Maka perlu dilakukan casting ke tipe aslinya, yaitu `int`, setelahnya barulah nilai bisa dioperasikan, yaitu `secret.(int) * 10`. + +Pada contoh kedua, `secret` berisikan array string. Kita memerlukan string tersebut untuk digabungkan dengan pemisah tanda koma. Maka perlu di-casting ke `[]string` terlebih dahulu sebelum bisa digunakan di `strings.Join()`, contohnya pada `strings.Join(secret.([]string), ", ")`. + +![Casting pada variabel bertipe `interface{}`](images/A.27_2_interface_casting.png) + +Teknik casting pada interface disebut dengan **type assertions**. + +## A.27.3. Casting Variabel Interface Kosong Ke Objek Pointer + +Variabel `interface{}` bisa menyimpan data apa saja, termasuk data objek, pointer, ataupun gabungan keduanya. Di bawah ini merupakan contoh penerapan interface untuk menampung data objek pointer. + +```go +type person struct { + name string + age int +} + +var secret interface{} = &person{name: "wick", age: 27} +var name = secret.(*person).name +fmt.Println(name) +``` + +Variabel `secret` dideklarasikan bertipe `interface{}` menampung referensi objek cetakan struct `person`. Cara casting dari `interface{}` ke struct pointer adalah dengan menuliskan nama struct-nya dan ditambahkan tanda asterisk (`*`) di awal, contohnya seperti `secret.(*person)`. Setelah itu barulah nilai asli bisa diakses. + +![Casting `interface{}` ke variabel objek](images/A.27_3_interface_pointer.png) + +## A.27.4. Kombinasi Slice, `map`, dan `interface{}` + +Tipe `[]map[string]interface{}` adalah salah satu tipe yang paling sering digunakan (menurut saya), karena tipe data tersebut bisa menjadi alternatif tipe slice struct. + +Pada contoh berikut, variabel `person` dideklarasikan berisi data slice `map` berisikan 2 item dengan key adalah `name` dan `age`. + +```go +var person = []map[string]interface{}{ + {"name": "Wick", "age": 23}, + {"name": "Ethan", "age": 23}, + {"name": "Bourne", "age": 22}, +} + +for _, each := range person { + fmt.Println(each["name"], "age is", each["age"]) +} +``` + +Dengan memanfaatkan slice dan `interface{}`, kita bisa membuat data array yang isinya adalah bisa apa saja. Silakan perhatikan contoh berikut. + +```go +var fruits = []interface{}{ + map[string]interface{}{"name": "strawberry", "total": 10}, + []string{"manggo", "pineapple", "papaya"}, + "orange", +} + +for _, each := range fruits { + fmt.Println(each) +} +``` + +--- + + diff --git a/28-reflect.md b/28-reflect.md new file mode 100644 index 000000000..8fde0beb2 --- /dev/null +++ b/28-reflect.md @@ -0,0 +1,171 @@ +# A.28. Reflect + +Reflection adalah teknik untuk inspeksi variabel, mengambil informasi dari variabel tersebut atau bahkan memanipulasinya. Cakupan informasi yang bisa didapatkan lewat reflection sangat luas, seperti melihat struktur variabel, tipe, nilai pointer, dan banyak lagi. + +Go menyediakan package `reflect`, berisikan banyak sekali fungsi untuk keperluan reflection. Di bab ini, kita akan belajar tentang dasar penggunaan package tersebut. + +Dari banyak fungsi yang tersedia di dalam package tersebut, ada 2 fungsi yang paling penting untuk diketahui, yaitu `reflect.ValueOf()` dan `reflect.TypeOf()`. + + - Fungsi `reflect.ValueOf()` akan mengembalikan objek dalam tipe `reflect.Value`, yang berisikan informasi yang berhubungan dengan nilai pada variabel yang dicari + - Sedangkan `reflect.TypeOf()` mengembalikan objek dalam tipe `reflect.Type`. Objek tersebut berisikan informasi yang berhubungan dengan tipe data variabel yang dicari + +## A.28.1. Mencari Tipe Data & Value Menggunakan Reflect + +Dengan reflection, tipe data dan nilai variabel dapat diketahui dengan mudah. Contoh penerapannya bisa dilihat pada kode berikut. + +```go +package main + +import "fmt" +import "reflect" + +func main() { + var number = 23 + var reflectValue = reflect.ValueOf(number) + + fmt.Println("tipe variabel :", reflectValue.Type()) + + if reflectValue.Kind() == reflect.Int { + fmt.Println("nilai variabel :", reflectValue.Int()) + } +} +``` + +![Pemanfaatan reflect](images/A.28_0_reflect.png) + +Fungsi `reflect.valueOf()` memiliki parameter yang bisa menampung segala jenis tipe data. Fungsi tersebut mengembalikan objek dalam tipe `reflect.Value`, yang berisikan informasi mengenai variabel yang bersangkutan. + +Objek `reflect.Value` memiliki beberapa method, salah satunya `Type()`. Method ini mengembalikan tipe data variabel yang bersangkutan dalam bentuk `string`. + +Statement `reflectValue.Int()` menghasilkan nilai `int` dari variabel `number`. Untuk menampilkan nilai variabel reflect, harus dipastikan dulu tipe datanya. Ketika tipe data adalah `int`, maka bisa menggunakan method `Int()`. Ada banyak lagi method milik struct `reflect.Value` yang bisa digunakan untuk pengambilan nilai dalam bentuk tertentu, contohnya: `reflectValue.String()` digunakan untuk mengambil nilai `string`, `reflectValue.Float64()` untuk nilai `float64`, dan lainnya. + +Perlu diketahui, fungsi yang digunakan harus sesuai dengan tipe data nilai yang ditampung variabel. Jika fungsi yang digunakan berbeda dengan tipe data variabelnya, maka akan menghasilkan error. Contohnya pada variabel menampung nilai bertipe `float64`, maka tidak bisa menggunakan method `String()`. + +Diperlukan adanya pengecekan tipe data nilai yang disimpan, agar pengambilan nilai bisa tepat. Salah satunya bisa dengan cara seperti kode di atas, yaitu dengan mengecek dahulu apa jenis tipe datanya menggunakan method `Kind()`, setelah itu diambil nilainya dengan method yang sesuai. + +List konstanta tipe data dan method yang bisa digunakan dalam refleksi di Go bisa dilihat di https://godoc.org/reflect#Kind + +## Pengaksesan Nilai Dalam Bentuk `interface{}` + +Jika nilai hanya diperlukan untuk ditampilkan ke output, bisa menggunakan `.Interface()`. Lewat method tersebut segala jenis nilai bisa diakses dengan mudah. + +```go +var number = 23 +var reflectValue = reflect.ValueOf(number) + +fmt.Println("tipe variabel :", reflectValue.Type()) +fmt.Println("nilai variabel :", reflectValue.Interface()) +``` + +Fungsi `Interface()` mengembalikan nilai interface kosong atau `interface{}`. Nilai aslinya sendiri bisa diakses dengan meng-casting interface kosong tersebut. + +```go +var nilai = reflectValue.Interface().(int) +``` + +## A.28.2. Pengaksesan Informasi Property Variabel Objek + +Reflect bisa digunakan untuk mengambil informasi semua property variabel objek cetakan struct, dengan catatan property-property tersebut bermodifier public. Langsung saja kita praktekan, siapkan sebuah struct bernama `student`. + +```go +type student struct { + Name string + Grade int +} +``` + +Buat method baru untuk struct tersebut, dengan nama method `getPropertyInfo()`. Method ini berisikan kode untuk mengambil dan menampilkan informasi tiap property milik struct `student`. + +```go +func (s *student) getPropertyInfo() { + var reflectValue = reflect.ValueOf(s) + + if reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + + var reflectType = reflectValue.Type() + + for i := 0; i < reflectValue.NumField(); i++ { + fmt.Println("nama :", reflectType.Field(i).Name) + fmt.Println("tipe data :", reflectType.Field(i).Type) + fmt.Println("nilai :", reflectValue.Field(i).Interface()) + fmt.Println("") + } +} +``` + +Terakhir, lakukan uji coba method di fungsi `main`. + +```go +func main() { + var s1 = &student{Name: "wick", Grade: 2} + s1.getPropertyInfo() +} +``` + +![Pengaksesan property menggunakan reflect](images/A.28_1_accessing_properties.png) + +Didalam method `getPropertyInfo` terjadi beberapa hal. Pertama objek `reflect.Value` dari variabel `s` diambil. Setelah itu dilakukan pengecekan apakah variabel objek tersebut merupakan pointer atau tidak (bisa dilihat dari `if reflectValue.Kind() == reflect.Ptr`, jika bernilai `true` maka variabel adalah pointer). jika ternyata variabel memang pointer, maka perlu diambil objek reflect aslinya dengan cara memanggil method `Elem()`. + +Setelah itu, dilakukan perulangan sebanyak jumlah property yang ada pada struct `student`. Method `NumField()` akan mengembalikan jumlah property publik yang ada dalam struct. + +Di tiap perulangan, informasi tiap property struct diambil berurutan dengan lewat method `Field()`. Method ini ada pada tipe `reflect.Value` dan `reflect.Type`. + + - `reflectType.Field(i).Name` akan mengembalikan nama property + - `reflectType.Field(i).Type` mengembalikan tipe data property + - `reflectValue.Field(i).Interface()` mengembalikan nilai property dalam bentuk `interface{}` + +Pengambilan informasi property, selain menggunakan indeks, bisa diambil berdasarkan nama field dengan menggunakan method `FieldByName()`. + +## A.28.3. Pengaksesan Informasi Method Variabel Objek + +Informasi mengenai method juga bisa diakses lewat reflect, syaratnya masih sama seperti pada pengaksesan proprerty, yaitu harus bermodifier public. + +Pada contoh dibawah ini informasi method `SetName` akan diambil lewat reflection. Siapkan method baru di struct `student`, dengan nama `SetName`. + +```go +func (s *student) SetName(name string) { + s.Name = name +} +``` + +Buat contoh penerapannya di fungsi `main`. + +```go +func main() { + var s1 = &student{Name: "john wick", Grade: 2} + fmt.Println("nama :", s1.Name) + + var reflectValue = reflect.ValueOf(s1) + var method = reflectValue.MethodByName("SetName") + method.Call([]reflect.Value{ + reflect.ValueOf("wick"), + }) + + fmt.Println("nama :", s1.Name) +} +``` + +![Eksekusi method lewat reflection](images/A.28_2_accessing_method_information.png) + +Pada kode di atas, disiapkan variabel `s1` yang merupakan instance struct `student`. Awalnya property `Name` variabel tersebut berisikan string `"john wick"`. + +Setelah itu, refleksi nilai objek tersebut diambil, refleksi method `SetName` juga diambil. Pengambilan refleksi method dilakukan menggunakan `MethodByName` dengan argument adalah nama method yang diinginkan, atau bisa juga lewat indeks method-nya (menggunakan `Method(i)`). + +Setelah refleksi method yang dicari sudah didapatkan, `Call()` dipanggil untuk eksekusi method. + +Jika eksekusi method diikuti pengisian parameter, maka parameternya harus ditulis dalam bentuk array `[]reflect.Value` berurutan sesuai urutan deklarasi parameter-nya. Dan nilai yang dimasukkan ke array tersebut harus dalam bentuk `reflect.Value` (gunakan `reflect.ValueOf()` untuk pengambilannya). + +```go +[]reflect.Value{ + reflect.ValueOf("wick"), +} +``` + +--- + + diff --git a/29-goroutine.md b/29-goroutine.md new file mode 100644 index 000000000..72886f1a0 --- /dev/null +++ b/29-goroutine.md @@ -0,0 +1,92 @@ +# A.29. Goroutine + +Goroutine mirip dengan thread thread, tapi sebenarnya bukan. Sebuah *native thread* bisa berisikan sangat banyak goroutine. Mungkin lebih pas kalau goroutine disebut sebagai **mini thread**. Goroutine sangat ringan, hanya dibutuhkan sekitar **2kB** memori saja untuk satu buah goroutine. Ekseksui goroutine bersifat *asynchronous*, menjadikannya tidak saling tunggu dengan goroutine lain. + +> Karena goroutine sangat ringan, maka eksekusi banyak goroutine bukan masalah. Akan tetapi jika jumlah goroutine sangat banyak sekali (contoh 1 juta goroutine dijalankan pada komputer dengan RAM terbatas), memang proses akan jauh lebih cepat selesai, tapi memory/RAM pasti bengkak. + +Goroutine merupakan salah satu bagian paling penting dalam *concurrent programming* di Go. Salah satu yang membuat goroutine sangat istimewa adalah eksekusi-nya dijalankan di multi core processor. Kita bisa tentukan berapa banyak core yang aktif, makin banyak akan makin cepat. + +Mulai bab A.29 ini hingga bab A.34, lalu dilanjut bab A.56 dan A.57, kita akan membahas tentang fitur-fitur yang disediakan Go untuk kebutuhan *concurrent programming*. + +> Concurrency atau konkurensi berbeda dengan paralel. Paralel adalah eksekusi banyak proses secara bersamaan. Sedangkan konkurensi adalah komposisi dari sebuah proses. Konkurensi merupakan struktur, sedangkan paralel adalah bagaimana eksekusinya berlangsung. + +## A.29.1. Penerapan Goroutine + +Untuk menerapkan goroutine, proses yang akan dieksekusi sebagai goroutine harus dibungkus kedalam sebuah fungsi. Pada saat pemanggilan fungsi tersebut, ditambahkan keyword `go` didepannya, dengan itu goroutine baru akan dibuat dengan tugas adalah menjalankan proses yang ada dalam fungsi tersebut. + +Berikut merupakan contoh implementasi sederhana tentang goroutine. Program di bawah ini menampilkan 10 baris teks, 5 dieksekusi dengan cara biasa, dan 5 lainnya dieksekusi sebagai goroutine baru. + +```go +package main + +import "fmt" +import "runtime" + +func print(till int, message string) { + for i := 0; i < till; i++ { + fmt.Println((i + 1), message) + } +} + +func main() { + runtime.GOMAXPROCS(2) + + go print(5, "halo") + print(5, "apa kabar") + + var input string + fmt.Scanln(&input) +} +``` + +Pada kode di atas, Fungsi `runtime.GOMAXPROCS(n)` digunakan untuk menentukan jumlah core yang diaktifkan untuk eksekusi program. + +Pembuatan goroutine baru ditandai dengan keyword `go`. Contohnya pada statement `go print(5, "halo")`, di situ fungsi `print()` dieksekusi sebagai goroutine baru. + +Fungsi `fmt.Scanln()` mengakibatkan proses jalannya aplikasi berhenti di baris itu (**blocking**) hingga user menekan tombol enter. Hal ini perlu dilakukan karena ada kemungkinan waktu selesainya eksekusi goroutine `print()` lebih lama dibanding waktu selesainya goroutine utama `main()`, mengingat bahwa keduanya sama-sama asnychronous. Jika itu terjadi, goroutine yang belum selesai secara paksa dihentikan prosesnya karena goroutine utama sudah selesai dijalankan. + +![Implementasi goroutine](images/A.29_1_goroutine.png) + +Bisa dilihat di output, tulisan `"halo"` dan `"apa kabar"` bermunculan selang-seling. Ini disebabkan karena statement `print(5, "halo")` dijalankan sebagai goroutine baru, menjadikannya tidak saling tunggu dengan `print(5, "apa kabar")`. + +Pada gambar di atas, program dieksekusi 2 kali. Hasil eksekusi pertama berbeda dengan kedua, penyebabnya adalah karena kita menggunakan 2 prosesor. Goroutine mana yang dieksekusi terlebih dahulu tergantung kedua prosesor tersebut. + +--- + +Berikut adalah penjelasan tambahan tentang beberapa fungsi yang baru kita pelajari di atas. + +## A.29.1.1. Penggunaan Fungsi `runtime.GOMAXPROCS()` + +Fungsi ini digunakan untuk menentukan jumlah core atau processor yang digunakan dalam eksekusi program. + +Jumlah yang diinputkan secara otomatis akan disesuaikan dengan jumlah asli *logical processor* yang ada. Jika jumlahnya lebih, maka dianggap menggunakan sejumlah prosesor yang ada. + +## A.29.1.2. Penggunaan Fungsi `fmt.Scanln()` + +Fungsi ini akan meng-capture semua karakter sebelum user menekan tombol enter, lalu menyimpannya pada variabel. + +```go +func Scanln(a ...interface{}) (n int, err error) +``` + +Kode di atas merupakan skema fungsi `fmt.Scanln()`. Fungsi tersebut bisa menampung parameter bertipe `interface{}` berjumlah tak terbatas. Tiap parameter akan menampung karakter-karakter inputan user yang sudah dipisah dengan tanda spasi. Agar lebih jelas, silakan perhatikan contoh berikut. + +```go +var s1, s2, s3 string +fmt.Scanln(&s1, &s2, &s3) + +// user inputs: "trafalgar d law" + +fmt.Println(s1) // trafalgar +fmt.Println(s2) // d +fmt.Println(s3) // law +``` + +Bisa dilihat pada kode di atas, untuk menampung inputan text `trafalgar d law`, dibutuhkan 3 buah variabel. Juga perlu diperhatikan bahwa yang disisipkan sebagai parameter pada pemanggilan fungsi `fmt.Scanln()` adalah referensi variabel, bukan nilai aslinya. + +--- + + diff --git a/3-gopath-dan-workspace.md b/3-gopath-dan-workspace.md new file mode 100644 index 000000000..21d29b29e --- /dev/null +++ b/3-gopath-dan-workspace.md @@ -0,0 +1,46 @@ +# A.3. GOPATH Dan Workspace + +Ada beberapa hal yang perlu disiapkan sebelum bisa masuk ke sesi pembuatan aplikasi menggunakan Go, yaitu setup workspace untuk Project yang akan dibuat. Dan di bab ini kita akan belajar bagaimana caranya. + +## A.3.0. Go Modules (Opsional) + +Mulai Go versi 1.11, inisialisasi projek dan manajemen dependencies bisa dilakukan dengan Go Modules. Jika berminat untuk menggunakannya, silakan langsung loncat ke [Bab A.60. Go Modules](/A-60-go-modules.html), lalu lanjut [Bab A.4. Instalasi Editor](/4-instalasi-editor.html). + +Jika dirasa masih bingung perihal Go Modules, penulis anjurkan untuk mengikuti pembahasan GOPATH pada bab ini terlelbih dahulu. + +## A.3.1. Variabel `GOPATH` + +**GOPATH** adalah variabel yang digunakan oleh Go sebagai rujukan lokasi dimana semua folder project disimpan. Gopath berisikian 3 buah sub folder: `src`, `bin`, dan `pkg`. + +Project di Go **harus** ditempatkan dalam `$GOPATH/src`. Sebagai contoh anda ingin membuat project dengan nama `belajar`, maka **harus** dibuatkan sebuah folder dengan nama `belajar`, ditempatkan dalam `src` (`$GOPATH/src/belajar`). + +> Path separator yang digunakan sebagai contoh di buku ini adalah slash `/`. Khusus pengguna Windows, path separator adalah backslah `\`. + +## A.3.2. Setup Workspace + +Lokasi folder yang akan dijadikan sebagai workspace bisa ditentukan sendiri. Anda bisa menggunakan alamat folder mana saja, bebas, tapi jangan gunakan path tempat dimana Go ter-install (tidak boleh sama dengan `GOROOT`). Lokasi tersebut harus didaftarkan dalam path variable dengan nama `GOPATH`. Sebagai contoh, penulis memilih path `$HOME/Documents/go`, maka saya daftarkan alamat tersebut. Caranya: + + - Bagi pengguna **Windows**, tambahkan path folder tersebut ke **path variable** dengan nama `GOPATH`. Setelah variabel terdaftar, cek apakah path sudah terdaftar dengan benar. + + > Sering terjadi `GOPATH` tidak dikenali meskipun variabel sudah didaftarkan. Jika hal seperti ini terjadi, restart CMD, lalu coba lagi. + + - Bagi pengguna Mac OS, export path ke `~/.bash_profile`. Untuk Linux, export ke `~/.bashrc` + + ```bash + $ echo "export GOPATH=$HOME/Documents/go" >> ~/.bash_profile + $ source ~/.bash_profile + ``` + + Cek apakah path sudah terdaftar dengan benar. + + ![Pengecekan `GOPATH` di sistem operasi non-Windows](images/A.3_1_path.png) + +Setelah `GOPATH` berhasil dikenali, perlu disiapkan 3 buah sub folder didalamnya, dengan kriteria sebagai berikut: + + - Folder `src`, adalah path dimana project Go disimpan + - Folder `pkg`, berisi file hasil kompilasi + - Folder `bin`, berisi file executable hasil build + +![Struktur folder dalam worskpace](images/A.3_2_workspace.png) + +Struktur diatas merupakan struktur standar workspace Go. Jadi pastikan penamaan dan hirarki folder adalah sama. diff --git a/30-channel.md b/30-channel.md new file mode 100644 index 000000000..827e9f90b --- /dev/null +++ b/30-channel.md @@ -0,0 +1,164 @@ +# A.30. Channel + +**Channel** digunakan untuk menghubungkan goroutine satu dengan goroutine lain. Mekanisme yang dilakukan adalah serah-terima data lewat channel tersebut. Dalam komunikasinya, sebuah channel difungsikan sebagai pengirim di sebuah goroutine, dan juga sebagai penerima di goroutine lainnya. Pengiriman dan penerimaan data pada channel bersifat **blocking** atau **synchronous**. + +![Analogi channel](images/A.30_1_analogy.png) + +Pada bab ini kita akan belajar mengenai pemanfaatan channel. + +## A.30.1. Penerapan Channel + +Channel merupakan sebuah variabel, dibuat dengan menggunakan kombinasi keyword `make` dan `chan`. Variabel channel memiliki satu tugas, menjadi pengirim, atau penerima data. + +Program berikut adalah contoh implementasi channel. 3 buah goroutine dieksekusi, di masing-masing goroutine terdapat proses pengiriman data lewat channel. Data tersebut akan diterima 3 kali di goroutine utama `main`. + +```go +package main + +import "fmt" +import "runtime" + +func main() { + runtime.GOMAXPROCS(2) + + var messages = make(chan string) + + var sayHelloTo = func(who string) { + var data = fmt.Sprintf("hello %s", who) + messages <- data + } + + go sayHelloTo("john wick") + go sayHelloTo("ethan hunt") + go sayHelloTo("jason bourne") + + var message1 = <-messages + fmt.Println(message1) + + var message2 = <-messages + fmt.Println(message2) + + var message3 = <-messages + fmt.Println(message3) +} +``` + +Pada kode di atas, variabel `messages` dideklarasikan bertipe channel `string`. Cara pembuatan channel yaitu dengan menuliskan keyword `make` dengan isi keyword `chan` diikuti dengan tipe data channel yang diinginkan. + +```go +var messages = make(chan string) +``` + +Selain itu disiapkan juga closure `sayHelloTo` yang tugasnya membuat sebuah pesan string yang kemudian dikirim via channel. Pesan string tersebut dikirim lewat channel `messages`. Tanda `<-` jika dituliskan di sebelah kiri nama variabel, berarti sedang berlangsung proses pengiriman data dari variabel yang berada di kanan lewat channel yang berada di kiri (pada konteks ini, variabel `data` dikirim lewat channel `messages`). + +```go +var sayHelloTo = func(who string) { + var data = fmt.Sprintf("hello %s", who) + messages <- data +} +``` + +Fungsi `sayHelloTo` dieksekusi tiga kali sebagai goroutine berbeda. Menjadikan tiga proses ini berjalan secara **asynchronous** atau tidak saling tunggu. + +> Sekali lagi perlu diingat bahwa eksekusi goroutine adalah *asynchronous*, sedangkan serah-terima data antar channel adalah *synchronous*. + +```go +go sayHelloTo("john wick") +go sayHelloTo("ethan hunt") +go sayHelloTo("jason bourne") +``` + +Dari ketiga fungsi tersebut, goroutine yang selesai paling awal akan mengirim data lebih dulu, datanya kemudian diterima variabel `message1`. Tanda `<-` jika dituliskan di sebelah kanan channel, menandakan proses penerimaan data dari channel yang di kanan, untuk disimpan ke variabel yang di kiri. + +```go +var message1 = <-messages +fmt.Println(message1) +``` + +Penerimaan channel bersifat blocking. Artinya statement `var message1 = <-messages` hingga setelahnya tidak akan dieksekusi sebelum ada data yang dikirim lewat channel. + +Kesemua data yang dikirim dari tiga goroutine berbeda tersebut datanya akan diterima secara berurutan oleh `message1`, `message2`, `message3`; untuk kemudian ditampilkan. + +![Implementasi channel](images/A.30_2_channel.png) + +Dari screenshot output di atas bisa dilihat bahwa text yang dikembalikan oleh `sayHelloTo` tidak selalu berurutan, meskipun penerimaan datanya adalah berurutan. Hal ini dikarenakan, pengiriman data adalah dari 3 goroutine yang berbeda, yang kita tidak tau mana yang prosesnya selesai lebih dulu. Goroutine yang dieksekusi lebih awal belum tentu selesai lebih awal, yang jelas proses yang selesai lebih awal datanya akan diterima lebih awal. + +Karena pengiriman dan penerimaan data lewat channel bersifat **blocking**, tidak perlu memanfaatkan sifat blocking dari fungsi sejenis `fmt.Scanln()` atau lainnya, untuk mengantisipasi goroutine utama `main` selesai sebelum ketiga goroutine di atas selesai. + +## A.30.2. Channel Sebagai Tipe Data Parameter + +Variabel channel bisa di-pass ke fungsi lain sebagai parameter. Cukup tambahkan keyword `chan` pada deklarasi parameter agar operasi pass channel variabel bisa dilakukan. + +Siapkan fungsi `printMessage` dengan parameter adalah channel. Lalu ambil data yang dikirimkan lewat channel tersebut untuk ditampilkan. + +```go +func printMessage(what chan string) { + fmt.Println(<-what) +} +``` + +Setelah itu ubah implementasi di fungsi `main`. + +```go +func main() { + runtime.GOMAXPROCS(2) + + var messages = make(chan string) + + for _, each := range []string{"wick", "hunt", "bourne"} { + go func(who string) { + var data = fmt.Sprintf("hello %s", who) + messages <- data + }(each) + } + + for i := 0; i < 3; i++ { + printMessage(messages) + } +} +``` + +Output program di atas sama dengan program sebelumnya. + +Parameter `what` fungsi `printMessage` bertipe channel `string`, bisa dilihat dari kode `chan string` pada cara deklarasinya. Operasi serah-terima data akan bisa dilakukan pada variabel tersebut, dan akan berdampak juga pada variabel `messages` di fungsi `main`. + +Passing data bertipe channel lewat parameter sifatnya **pass by reference**, yang ditransferkan adalah pointer datanya, bukan nilai datanya. + +![Parameter channel](images/A.30_3_channel_param.png) + +--- + +Berikut merupakan penjelasan tambahan untuk kode di atas. + +### A.30.2.1. Iterasi Data Slice/Array Langsung Pada Saat Inisialisasi + +Data slice yang baru di-inisialisasi bisa langsung di-iterasi, caranya mudah dengan menuliskanya langsung setelah keyword `range`. + +```go +for _, each := range []string{"wick", "hunt", "bourne"} { + // ... +} +``` + +### A.30.2.2. Eksekusi Goroutine Pada IIFE + +Eksekusi goroutine tidak harus pada fungsi atau closure yang sudah terdefinisi. Sebuah IIFE juga bisa dijalankan sebagai goroutine baru. Caranya dengan langsung menambahkan keyword `go` pada waktu deklarasi-eksekusi IIFE-nya. + +```go +var messages = make(chan string) + +go func(who string) { + var data = fmt.Sprintf("hello %s", who) + messages <- data +}("wick") + +var message = <-messages +fmt.Println(message) +``` + +--- + + diff --git a/31-buffered-channel.md b/31-buffered-channel.md new file mode 100644 index 000000000..ed035fc08 --- /dev/null +++ b/31-buffered-channel.md @@ -0,0 +1,59 @@ +# A.31. Buffered Channel + +Proses transfer data pada channel secara default dilakukan dengan cara **un-buffered**, tidak di-buffer di memori. Ketika terjadi proses kirim data via channel dari sebuah goroutine, maka harus ada goroutine lain yang bertugas menerima data dari channel yang sama, dengan proses serah-terima yang bersifat blocking. Maksudnya, baris kode setelah kode pengiriman dan penerimaan data tidak akan akan diproses sebelum proses serah-terima itu sendiri selesai. + +Buffered channel sedikit berbeda. Pada channel jenis ini, ditentukan angka jumlah buffer-nya. Angka tersebut menjadi penentu jumlah data yang bisa dikirimkan bersamaan. Selama jumlah data yang dikirim tidak melebihi jumlah buffer, maka pengiriman akan berjalan **asynchronous** (tidak blocking). + +Ketika jumlah data yang dikirim sudah melewati batas buffer, maka pengiriman data hanya bisa dilakukan ketika salah satu data yang sudah terkirim adalah sudah diambil dari channel di goroutine penerima, sehingga ada slot channel yang kosong. + +Proses pengiriman data pada buffered channel adalah *asynchronous* ketika jumlah data yang dikirim tidak melebihi batas buffer. Namun pada bagian channel penerimaan data selalu bersifat *synchronous*. + +![Analogi buffered channel](images/A.31_1_anatomy.png) + +## A.31.1. Penerapan Buffered Channel + +Penerapan buffered channel pada dasarnya mirip seperti channel biasa. Perbedannya hanya pada penulisan deklarasinya, perlu ditambahkan angka buffer sebagai argumen `make()`. + +Berikut adalah contoh penerapan buffered channel. Program dibawah ini merupakan pembuktian bahwa pengiriman data lewat buffered channel adalah asynchronous selama jumlah data yang sedang di-buffer oleh channel tidak melebihi kapasitas buffer. + +```go +package main + +import "fmt" +import "runtime" + +func main() { + runtime.GOMAXPROCS(2) + + messages := make(chan int, 2) + + go func() { + for { + i := <-messages + fmt.Println("receive data", i) + } + }() + + for i := 0; i < 5; i++ { + fmt.Println("send data", i) + messages <- i + } +} +``` + +Pada kode di atas, parameter kedua fungsi `make()` adalah representasi jumlah buffer. Perlu diperhatikan bahwa nilai buffered channel dimulai dari `0`. Ketika nilainya adalah **2** brarti jumlah buffer maksimal ada **3**. + +Bisa dilihat terdapat IIFE goroutine yang isinya proses penerimaan data dari channel `messages`, untuk kemudian datanya ditampilkan. Setelah goroutine tersebut dieksekusi, perulangan dijalankan dengan di-masing-masing perulangan dilakukan pengiriman data. Total ada 5 data dikirim lewat channel `messages` secara sekuensial. + +![Implementasi buffered channel](images/A.31_2_buffered_channel.png) + +Bisa dilihat output di atas, pada proses pengiriman data ke-4, diikuti dengan proses penerimaan data; yang kedua proses tersebut berlangsung secara blocking. + +Pengiriman data indeks ke 0, 1, 2 dan 3 akan berjalan secara asynchronous, hal ini karena channel ditentukan nilai buffer-nya sebanyak 3 (ingat, jika nilai buffer adalah 3, maka 4 data yang akan di-buffer). Pengiriman selanjutnya (indeks 4 dan 5) hanya akan terjadi jika ada salah satu data dari ke-empat data yang sebelumnya telah dikirimkan sudah diterima (dengan serah terima data yang bersifat blocking). Setelahnya, pengiriman data kembali dilakukan secara asynchronous (karena sudah ada slot buffer ada yang kosong). + +--- + + diff --git a/32-channel-select.md b/32-channel-select.md new file mode 100644 index 000000000..030cbcc08 --- /dev/null +++ b/32-channel-select.md @@ -0,0 +1,86 @@ +# A.32. Channel - Select + +Disediakannya channel membuat engineer menjadi mudah dalam me-manage goroutine. Namun perlu di-ingat, meskipun lewat channel manajemen goroutine menjadi mudah, fungsi utama channel bukan untuk kontrol, melainkan untuk sharing data antar goroutine. + +> Nantinya pada [Bab A.56. Waitgroup](/56-waitgroup.html) akan dibahas secara komprehensif bagaimana cara optimal mengontrol goroutine. + +Ada kalanya kita butuh tak hanya satu channel saja untuk melakukan komunikasi data antar goroutine. Tergantung jenis kasusnya, sangat mungkin lebih dari satu channel dibutuhkan. Nah, disitulah kegunaan dari `select`. Select ini mempermudah kontrol komunikasi data lewat satu ataupun banyak channel. + +Cara penggunaan `switch` untuk kontrol channel sama seperti penggunaan `switch` untuk seleksi kondisi. + +## A.32.1. Penerapan Keyword `select` + +Program berikut merupakan contoh sederhana penerapan select dalam channel. Dipersiapkan 2 buah goroutine, satu untuk pencarian rata-rata, dan satu untuk nilai tertinggi. Hasil operasi di masing-masing goroutine dikirimkan ke fungsi `main()` via channel (ada dua channel). Di fungsi `main()` sendiri, data tersebut diterima dengan memanfaatkan keyword `select`. + +Ok, langsung saja kita praktek. Pertama, siapkan 2 fungsi yang sudah dibahas di atas. Fungsi pertama digunakan untuk mencari rata-rata, dan fungsi kedua untuk penentuan nilai tertinggi dari sebuah slice. + +```go +package main + +import "fmt" +import "runtime" + +func getAverage(numbers []int, ch chan float64) { + var sum = 0 + for _, e := range numbers { + sum += e + } + ch <- float64(sum) / float64(len(numbers)) +} + +func getMax(numbers []int, ch chan int) { + var max = numbers[0] + for _, e := range numbers { + if max < e { + max = e + } + } + ch <- max +} +``` + +Kedua fungsi tersebut dijalankan sebagai goroutine. Di akhir blok masing-masing fungsi, hasil kalkulasi dikirimkan via channel yang sudah dipersiapkan, yaitu `ch1` untuk menampung data rata-rata, `ch2` untuk data nilai tertinggi. + +Ok lanjut, buat implementasinya pada fungsi `main()`. + +```go +func main() { + runtime.GOMAXPROCS(2) + + var numbers = []int{3, 4, 3, 5, 6, 3, 2, 2, 6, 3, 4, 6, 3} + fmt.Println("numbers :", numbers) + + var ch1 = make(chan float64) + go getAverage(numbers, ch1) + + var ch2 = make(chan int) + go getMax(numbers, ch2) + + for i := 0; i < 2; i++ { + select { + case avg := <-ch1: + fmt.Printf("Avg \t: %.2f \n", avg) + case max := <-ch2: + fmt.Printf("Max \t: %d \n", max) + } + } +} +``` + +Pada kode di atas, pengiriman data pada channel `ch1` dan `ch2` dikontrol menggunakan `select`. Terdapat 2 buah `case` kondisi penerimaan data dari kedua channel tersebut. + + - Kondisi `case avg := <-ch1` akan terpenuhi ketika ada penerimaan data dari channel `ch1`, yang kemudian akan ditampung oleh variabel `avg`. + - Kondisi `case max := <-ch2` akan terpenuhi ketika ada penerimaan data dari channel `ch2`, yang kemudian akan ditampung oleh variabel `max`. + +Karena ada 2 buah channel, maka perlu disiapkan perulangan 2 kali sebelum penggunaan keyword `select`. + +![Contoh penerapan channel select](images/A.32_1_channel_select.png) + +Cukup mudah bukan? + +--- + + diff --git a/33-channel-range-close.md b/33-channel-range-close.md new file mode 100644 index 000000000..758dbe9e0 --- /dev/null +++ b/33-channel-range-close.md @@ -0,0 +1,77 @@ +# A.33. Channel - Range dan Close + +Proses retrieving data dari banyak channel bisa lebih mudah dilakukan dengan memanfaatkan kombinasi keyword `for` - `range`. + +`for` - `range` jika diterapkan pada channel berfungsi untuk handle penerimaan data. Setiap kali ada pengiriman data via channel, maka akan men-trigger perulangan `for` - `range`. Perulangan akan berlangsung terus-menerus seiring pengiriman data ke channel yang dipergunakan. Dan perulangan hanya akan berhenti jika channel yang digunakan tersebut di **close** atau di-non-aktifkan. Fungsi `close` digunakan utuk me-non-aktifkan channel. + +Channel yang sudah di-close tidak bisa digunakan lagi baik untuk menerima data ataupun untuk mengirim data, itulah mengapa perulangan `for` - `range` juga berhenti. + +## A.33.1. Penerapan `for` - `range` - `close` Pada Channel + +Berikut adalah contoh program menggunakan `for` - `range` untuk menerima data dari channel. + +Ok, pertama siapkan fungsi `sendMessage()` yang tugasnya mengirim data via channel. Didalam fungsi ini dijalankan perulangan sebanyak 20 kali, ditiap perulangannya data dikirim ke channel. Channel di-close setelah semua data selesai dikirim. + +```go +func sendMessage(ch chan<- string) { + for i := 0; i < 20; i++ { + ch <- fmt.Sprintf("data %d", i) + } + close(ch) +} +``` + +Siapkan juga fungsi `printMessage()` untuk handle penerimaan data. Didalam fungsi tersebut, channel di-looping menggunakan `for` - `range`. Di tiap looping, data yang diterima dari channel ditampilkan. + +```go +func printMessage(ch <-chan string) { + for message := range ch { + fmt.Println(message) + } +} +``` + +Buat channel baru dalam fungsi `main()`, jalankan `sendMessage()` sebagai goroutine (dengan ini 20 data yang berada dalam fungsi tersebut dikirimkan via goroutine baru). Tak lupa jalankan juga fungsi `printMessage()`. + +```go +func main() { + runtime.GOMAXPROCS(2) + + var messages = make(chan string) + go sendMessage(messages) + printMessage(messages) +} +``` + +Setelah 20 data sukses dikirim dan diterima, channel `ch` di-non-aktifkan (`close(ch)`). Membuat perulangan data channel dalam `printMessage()` juga akan berhenti. + +![Penerapan for-range-close pada channel](images/A.33_1_for_range_close.png) + +--- + +Berikut adalah penjelasan tambahan mengenai channel. + +## A.33.1.1. Channel Direction + +Ada yang unik dengan fitur parameter channel yang disediakan oleh Go. Level akses channel bisa ditentukan, apakah hanya sebagai penerima, pengirim, atau penerima sekaligus pengirim. Konsep ini disebut dengan **channel direction**. + +Cara pemberian level akses adalah dengan menambahkan tanda `<-` sebelum atau setelah keyword `chan`. Untuk lebih jelasnya bisa dilihat di list berikut. + +| Sintaks | Penjelasan | +| :------- | :--------- | +| `ch chan string` | Parameter `ch` bisa digunakan untuk **mengirim** dan **menerima** data | +| `ch chan<- string` | Parameter `ch` hanya bisa digunakan untuk **mengirim** data | +| `ch <-chan string` | Parameter `ch` hanya bisa digunakan untuk **menerima** data | + +Pada kode di atas bisa dilihat bahwa secara default channel akan memiliki kemampuan untuk mengirim dan menerima data. Untuk mengubah channel tersebut agar hanya bisa mengirim atau menerima saja, dengan memanfaatkan simbol `<-`. + +Sebagai contoh fungsi `sendMessage(ch chan<- string)` yang parameter `ch` dideklarasikan dengan level akses untuk pengiriman data saja. Channel tersebut hanya bisa digunakan untuk mengirim, contohnya: `ch <- fmt.Sprintf("data %d", i)`. + +Dan sebaliknya pada fungsi `printMessage(ch <-chan string)`, channel `ch` hanya bisa digunakan untuk menerima data saja. + +--- + + diff --git a/34-channel-timeout.md b/34-channel-timeout.md new file mode 100644 index 000000000..bacdb80a7 --- /dev/null +++ b/34-channel-timeout.md @@ -0,0 +1,72 @@ +# A.34. Channel - Timeout + +Teknik channel timeout digunakan untuk mengontrol penerimaan data dari channel mengacu ke kapan waktu diterimanya data, dengan durasi timeout bisa kita tentukan sendiri. + +Ketika tidak ada aktivitas penerimaan data dalam durasi yang sudah ditentukan, maka blok timeout dieksekusi. + +## A.34.1. Penerapan Channel Timeout + +Berikut adalah program sederhana contoh pengaplikasian timeout pada channel. Sebuah goroutine baru dijalankan dengan tugas mengirimkan data setiap interval tertentu, dengan durasi interval-nya sendiri adalah acak/random. + +```go +package main + +import "fmt" +import "math/rand" +import "runtime" +import "time" + +func sendData(ch chan<- int) { + for i := 0; true; i++ { + ch <- i + time.Sleep(time.Duration(rand.Int()%10+1) * time.Second) + } +} +``` + +Selanjutnya, disiapkan perulangan tanpa henti, yang di setiap perulangan ada seleksi kondisi channel menggunakan `select`. + +```go +func retreiveData(ch <-chan int) { + loop: + for { + select { + case data := <-ch: + fmt.Print(`receive data "`, data, `"`, "\n") + case <-time.After(time.Second * 5): + fmt.Println("timeout. no activities under 5 seconds") + break loop + } + } +} +``` + +Ada 2 blok kondisi pada `select` tersebut. + + - Kondisi `case data := <-messages:`, akan terpenuhi ketika ada serah terima data pada channel `messages`. + - Kondisi `case <-time.After(time.Second * 5):`, akan terpenuhi ketika tidak ada aktivitas penerimaan data dari channel dalam durasi 5 detik. Blok inilah yang kita sebut sebagai blok timeout. + +Terakhir, kedua fungsi tersebut dipanggil di `main()`. + +```go +func main() { + rand.Seed(time.Now().Unix()) + runtime.GOMAXPROCS(2) + + var messages = make(chan int) + + go sendData(messages) + retreiveData(messages) +} +``` + +Muncul output setiap kali ada penerimaan data dengan delay waktu acak. Ketika tidak ada aktifitas penerimaan dari channel dalam durasi 5 detik, perulangan pengecekkan channel diberhentikan. + +![Channel timeout](images/A.34_1_channel_delay.png) + +--- + + diff --git a/35-defer-exit.md b/35-defer-exit.md new file mode 100644 index 000000000..6cbbe03e8 --- /dev/null +++ b/35-defer-exit.md @@ -0,0 +1,136 @@ +# A.35. Defer & Exit + +**Defer** digunakan untuk mengakhirkan eksekusi sebuah statement tepat sebelum blok fungsi selesai. Sedangkan **Exit** digunakan untuk menghentikan program secara paksa (ingat, menghentikan program, tidak seperti `return` yang hanya menghentikan blok kode). + +## A.35.1. Penerapan keyword `defer` + +Seperti yang sudah dijelaskan secara singkat di atas, bahwa defer digunakan untuk mengakhirkan eksekusi baris kode **dalam skope blok fungsi**. Ketika eksekusi blok sudah hampir selesai, statement yang di-defer dijalankan. + +Defer bisa ditempatkan di mana saja, awal maupun akhir blok. Tetapi tidak mempengaruhi kapan waktu dieksekusinya, akan selalu dieksekusi di akhir. + +```go +package main + +import "fmt" + +func main() { + defer fmt.Println("halo") + fmt.Println("selamat datang") +} +``` + +Output: + +![Penerapan `defer`](images/A.35_1_defer.png) + +Keyword `defer` di atas akan mengakhirkan ekseusi `fmt.Println("halo")`, efeknya pesan `"halo"` akan muncul setelah `"selamat datang"`. + +Statement yang di-defer akan tetap muncul meskipun blok kode diberhentikan ditengah jalan menggunakan `return`. Contohnya seperti pada kode berikut. + +```go +func main() { + orderSomeFood("pizza") + orderSomeFood("burger") +} + +func orderSomeFood(menu string) { + defer fmt.Println("Terimakasih, silakan tunggu") + if menu == "pizza" { + fmt.Print("Pilihan tepat!", " ") + fmt.Print("Pizza ditempat kami paling enak!", "\n") + return + } + + fmt.Println("Pesanan anda:", menu) +} +``` + +Output: + +![Penerapan `defer` dengan `return`](images/A.35_2_defer_return.png) + +Info tambahan, ketika ada banyak statement yang di-defer, maka kesemuanya akan dieksekusi di akhir secara berurutan. + +## A.35.2. Kombinasi `defer` dan IIFE + +Penulis ingatkan lagi bahwa eksekusi defer adalah di akhir blok fungsi, bukan blok lainnya seperti blok seleksi kondisi. + +```go +func main() { + number := 3 + + if number == 3 { + fmt.Println("halo 1") + defer fmt.Println("halo 3") + } + + fmt.Println("halo 2") +} +``` + +Output: + +``` +halo 1 +halo 2 +halo 3 +``` + +Pada contoh di atas `halo 3` akan tetap di print setelah `halo 2` meskipun statement defer dipergunakan dalam blok seleksi kondisi `if`. Hal ini karena defer eksekusinya terjadi pada akhir blok fungsi (dalam contoh di atas `main()`), bukan pada akhir blok `if`. + +Agar `halo 3` bisa dimunculkan di akhir blok `if`, maka harus dibungkus dengan IIFE. Contoh: + +```go +func main() { + number := 3 + + if number == 3 { + fmt.Println("halo 1") + func() { + defer fmt.Println("halo 3") + }() + } + + fmt.Println("halo 2") +} +``` + +Output: + +``` +halo 1 +halo 3 +halo 2 +``` + +Bisa dilihat `halo 3` muncul sebelum `halo 2`, karena dalam blok seleksi kondisi `if` eksekusi defer terjadi dalam blok fungsi anonymous (IIFE). + +## A.35.3. Penerapan Fungsi `os.Exit()` + +Exit digunakan untuk menghentikan program secara paksa pada saat itu juga. Semua statement setelah exit tidak akan di eksekusi, termasuk juga defer. + +Fungsi `os.Exit()` berada dalam package `os`. Fungsi ini memiliki sebuah parameter bertipe numerik yang wajib diisi. Angka yang dimasukkan akan muncul sebagai **exit status** ketika program berhenti. + +```go +package main + +import "fmt" +import "os" + +func main() { + defer fmt.Println("halo") + os.Exit(1) + fmt.Println("selamat datang") +} +``` + +Meskipun `defer fmt.Println("halo")` ditempatkan sebelum `os.Exit()`, statement tersebut tidak akan dieksekusi, karena di-tengah fungsi program dihentikan secara paksa. + +![Penerapan `exit`](images/A.35_3_exit.png) + +--- + + diff --git a/36-error-panic-recover.md b/36-error-panic-recover.md new file mode 100644 index 000000000..a97c626ee --- /dev/null +++ b/36-error-panic-recover.md @@ -0,0 +1,217 @@ +# A.36. Error, Panic, dan Recover + +Error merupakan topik yang sangat penting dalam pemrograman Go. Di bagian ini kita akan belajar mengenai pemanfaatan error dan cara membuat custom error sendiri. Selain itu, kita juga akan belajar tentang penggunaan **panic** untuk memunculkan panic error, dan **recover** untuk mengatasinya. + +## A.36.1. Pemanfaatan Error + +`error` merupakan sebuah tipe. Error memiliki memiliki 1 buah property berupa method `Error()`, method ini mengembalikan detail pesan error dalam string. Error termasuk tipe yang isinya bisa `nil`. + +Di Go, banyak sekali fungsi yang mengembalikan nilai balik lebih dari satu. Biasanya, salah satu kembalian adalah bertipe `error`. Contohnya seperti pada fungsi `strconv.Atoi()`. Fungsi tersebut digunakan untuk konversi data string menjadi numerik. Fungsi ini mengembalikan 2 nilai balik. Nilai balik pertama adalah hasil konversi, dan nilai balik kedua adalah `error`. Ketika konversi berjalan mulus, nilai balik kedua akan bernilai `nil`. Sedangkan ketika konversi gagal, penyebabnya bisa langsung diketahui dari error yang dikembalikan. + +Dibawah ini merupakan contoh program sederhana untuk deteksi inputan dari user, apakah numerik atau bukan. Dari sini kita akan belajar mengenai pemanfaatan error. + +```go +package main + +import ( + "fmt" + "strconv" +) + +func main() { + var input string + fmt.Print("Type some number: ") + fmt.Scanln(&input) + + var number int + var err error + number, err = strconv.Atoi(input) + + if err == nil { + fmt.Println(number, "is number") + } else { + fmt.Println(input, "is not number") + fmt.Println(err.Error()) + } +} +``` + +Jalankan program, maka muncul tulisan `"Type some number: "`. Ketik angka bebas, jika sudah maka enter. + +Statement `fmt.Scanln(&input)` dipergunakan untuk men-capture inputan yang diketik oleh user sebelum dia menekan enter, lalu menyimpannya sebagai string ke variabel `input`. + +Selanjutnya variabel tersebut dikonversi ke tipe numerik menggunakan `strconv.Atoi()`. Fungsi tersebut mengembalikan 2 data, ditampung oleh `number` dan `err`. + +Data pertama (`number`) berisi hasil konversi. Dan data kedua `err`, berisi informasi errornya (jika memang terjadi error ketika proses konversi). + +Setelah itu dilakukan pengecekkan, ketika tidak ada error, `number` ditampilkan. Dan jika ada error, `input` ditampilkan beserta pesan errornya. + +Pesan error bisa didapat dari method `Error()` milik tipe `error`. + +![Penerapan error](images/A.36_1_error.png) + +## A.36.2. Membuat Custom Error + +Selain memanfaatkan error hasil kembalian suatu fungsi internal yang tersedia, kita juga bisa membuat objek error sendiri dengan menggunakan fungsi `errors.New()` (harus import package `errors` terlebih dahulu). + +Pada contoh berikut ditunjukan bagaimana cara membuat custom error. Pertama siapkan fungsi dengan nama `validate()`, yang nantinya digunakan untuk pengecekan input, apakah inputan kosong atau tidak. Ketika kosong, maka error baru akan dibuat. + +```go +package main + +import ( + "errors" + "fmt" + "strings" +) + +func validate(input string) (bool, error) { + if strings.TrimSpace(input) == "" { + return false, errors.New("cannot be empty") + } + return true, nil +} +``` + +Selanjutnya di fungsi main, buat proses sederhana untuk capture inputan user. Manfaatkan fungsi `validate()` untuk mengecek inputannya. + +```go +func main() { + var name string + fmt.Print("Type your name: ") + fmt.Scanln(&name) + + if valid, err := validate(name); valid { + fmt.Println("halo", name) + } else { + fmt.Println(err.Error()) + } +} +``` + +Fungsi `validate()` mengembalikan 2 data. Data pertama adalah nilai `bool` yang menandakan inputan apakah valid atau tidak. Data ke-2 adalah pesan error-nya (jika inputan tidak valid). + +Fungsi `strings.TrimSpace()` digunakan untuk menghilangkan karakter spasi sebelum dan sesudah string. Ini dibutuhkan karena user bisa saja menginputkan spasi lalu enter. + +Ketika inputan tidak valid, maka error baru dibuat dengan memanfaatkan fungsi `errors.New()`. Selain itu objek error juga bisa dibuat lewat fungsi `fmt.Errorf()`. + +![Custom error](images/A.36_2_custom_error.png) + +## A.36.3. Penggunaan `panic` + +Panic digunakan untuk menampilkan *stack trace* error sekaligus menghentikan flow goroutine (karena `main()` juga merupakan goroutine, maka behaviour yang sama juga berlaku). Setelah ada panic, proses akan terhenti, apapun setelah tidak di-eksekusi kecuali proses yang sudah di-defer sebelumnya (akan muncul sebelum panic error). + +Panic menampilkan pesan error di console, sama seperti `fmt.Println()`. Informasi error yang ditampilkan adalah stack trace error, jadi sangat mendetail dan heboh. + +Kembali ke koding, pada program yang telah kita buat tadi, ubah `fmt.Println()` yang berada di dalam blok kondisi `else` pada fungsi main menjadi `panic()`, lalu tambahkan `fmt.Println()` setelahnya. + +```go +func main() { + var name string + fmt.Print("Type your name: ") + fmt.Scanln(&name) + + if valid, err := validate(name); valid { + fmt.Println("halo", name) + } else { + fmt.Println(err.Error()) + fmt.Println("end") + } +} +``` + +Jalankan program lalu langsung tekan enter, maka panic error muncul dan baris kode setelahnya tidak dijalankan. + +![Menampilkan error menggunakan panic](images/A.36_3_panic.png) + +## A.36.4. Penggunaan `recover` + +Recover berguna untuk meng-handle panic error. Pada saat panic error muncul, recover men-take-over goroutine yang sedang panic (pesan panic tidak akan muncul). + +Ok, mari kita modif sedikit fungsi di-atas untuk mempraktekkan bagaimana cara penggunaan recover. Tambahkan fungsi `catch()`, dalam fungsi ini terdapat statement `recover()` yang dia akan mengembalikan pesan panic error yang seharusnya muncul. + +Untuk menggunakan recover, fungsi/closure/IIFE dimana `recover()` berada harus dieksekusi dengan cara di-defer. + +```go +func catch() { + if r := recover(); r != nil { + fmt.Println("Error occured", r) + } else { + fmt.Println("Application running perfectly") + } +} + +func main() { + defer catch() + + var name string + fmt.Print("Type your name: ") + fmt.Scanln(&name) + + if valid, err := validate(name); valid { + fmt.Println("halo", name) + } else { + panic(err.Error()) + fmt.Println("end") + } +} +``` + +Output: + +![Handle panic menggunakan recover](images/A.36_4_recover.png) + +## A.36.5. Pemanfaatan `recover` pada IIFE + +Contoh penerapan recover pada IIFE: + +```go +func main() { + defer func() { + if r := recover(); r != nil { + fmt.Println("Panic occured", r) + } else { + fmt.Println("Application running perfectly") + } + }() + + panic("some error happen") +} +``` + +Dalam real-world development, ada kalanya recover dibutuhkan tidak dalam blok fungsi terluar, tetapi dalam blok fungsi yg lebih spesifik. + +Silakan perhatikan contoh kode recover perulangan berikut. Umumnya, jika terjadi panic error, maka proses proses dalam scope blok fungsi akan terjenti, mengakibatkan perulangan juga akan terhenti secara paksa. Pada contoh berikut kita coba terapkan cara handle panic error tanpa menghentikan perulangan itu sendiri. + +```go +func main() { + data := []string{"superman", "aquaman", "wonder woman"} + + for _, each := range data { + + func() { + + // recover untuk IIFE dalam perulangan + defer func() { + if r := recover(); r != nil { + fmt.Println("Panic occured on looping", each, "| message:", r) + } else { + fmt.Println("Application running perfectly") + } + }() + + panic("some error happen") + }() + + } +} +``` + +Pada kode di atas, bisa dilihat di dalam perulangan terdapat sebuah IIFE untuk recover panic dan juga ada kode untuk men-trigger panic error secara paksa. Ketika panic error terjadi, maka idealnya perulangan terhenti, tetapi pada contoh di atas tidak, dikarenakan operasi dalam perulangan sudah di bungkus dalam IIFE dan seperti yang kita tau sifat panic error adalah menghentikan proses secara paksa dalam scope blok fungsi. + +--- + + diff --git a/37-string-format.md b/37-string-format.md new file mode 100644 index 000000000..bad886dfa --- /dev/null +++ b/37-string-format.md @@ -0,0 +1,275 @@ +# A.37. Layout Format String + +Di bab-bab sebelumnya kita telah banyak menggunakan layout format string seperti `%s`, `%d`, `%.2f`, dan lainnya; untuk keperluan menampilkan output ke layar ataupun untuk memformat string. + +Layout format string digunakan dalam konversi data ke bentuk string. Contohnya seperti `%.3f` yang untuk konversi nilai `double` ke `string` dengan 3 digit desimal. + +## A.37.1. Persiapan + +Pada bab ini kita akan mempelajari satu per satu layout format string yang tersedia di Golang. Kode berikut adalah sample data yang akan kita digunakan sebagai contoh. + +```go +type student struct { + name string + height float64 + age int32 + isGraduated bool + hobbies []string +} + +var data = student{ + name: "wick", + height: 182.5, + age: 26, + isGraduated: false, + hobbies: []string{"eating", "sleeping"}, +} +``` + +## A.37.2. Layout Format `%b` + +Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 2 (biner). + +```go +fmt.Printf("%b\n", data.age) +// 11010 +``` + +## A.37.3. Layout Format `%c` + +Digunakan untuk memformat data numerik yang merupakan kode unicode, menjadi bentuk string karakter unicode-nya. + +```go +fmt.Printf("%c\n", 1400) +// ո + +fmt.Printf("%c\n", 1235) +// ӓ +``` + +## A.37.4. Layout Format `%d` + +Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 10 (basis bilangan yang kita gunakan). + +```go +fmt.Printf("%d\n", data.age) +// 26 +``` + +## A.37.5. Layout Format `%e` atau `%E` + +Digunakan untuk memformat data numerik desimal ke dalam bentuk notasi numerik standar [Scientific notation](https://en.wikipedia.org/wiki/Scientific_notation). + +```go +fmt.Printf("%e\n", data.height) +// 1.825000e+02 + +fmt.Printf("%E\n", data.height) +// 1.825000E+02 +``` + +**1.825000E+02** maksudnya adalah **1.825 x 10^2**, dan hasil operasi tersebut adalah sesuai dengan data asli = **182.5**. + +Perbedaan antara `%e` dan `%E` hanya pada bagian huruf besar kecil karakter `e` pada hasil. + +## A.37.6. Layout Format `%f` atau `%F` + +`%F` adalah alias dari `%f`. Keduanya memiliki fungsi yang sama. + +Berfungsi untuk memformat data numerik desimal, dengan lebar desimal bisa ditentukan. Secara default lebar digit desimal adalah 6 digit. + +```go +fmt.Printf("%f\n", data.height) +// 182.500000 + +fmt.Printf("%.9f\n", data.height) +// 182.500000000 + +fmt.Printf("%.2f\n", data.height) +// 182.50 + +fmt.Printf("%.f\n", data.height) +// 182 +``` + +## A.37.7. Layout Format `%g` atau `%G` + +`%G` adalah alias dari `%g`. Keduanya memiliki fungsi yang sama. + +Berfungsi untuk memformat data numerik desimal, dengan lebar desimal bisa ditentukan. Lebar kapasitasnya sangat besar, pas digunakan untuk data yang jumlah digit desimalnya cukup banyak. + +Bisa dilihat pada kode berikut perbandingan antara `%e`, `%f`, dan `%g`. + +```go +fmt.Printf("%e\n", 0.123123123123) +// 1.231231e-01 + +fmt.Printf("%f\n", 0.123123123123) +// 0.123123 + +fmt.Printf("%g\n", 0.123123123123) +// 0.123123123123 +``` + +Perbedaan lainnya adalah pada `%g`, lebar digit desimal adalah sesuai dengan datanya, tidak bisa dicustom seperti pada `%f`. + +```go +fmt.Printf("%g\n", 0.12) +// 0.12 + +fmt.Printf("%.5g\n", 0.12) +// 0.12 +``` + +## A.37.8. Layout Format `%o` + +Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 8 (oktal). + +```go +fmt.Printf("%o\n", data.age) +// 32 +``` + +## A.37.9. Layout Format `%p` + +Digunakan untuk memformat data pointer, mengembalikan alamat pointer referensi variabel-nya. + +Alamat pointer dituliskan dalam bentuk numerik berbasis 16 dengan prefix `0x`. + +```go +fmt.Printf("%p\n", &data.name) +// 0x2081be0c0 +``` + +## A.37.10. Layout Format `%q` + +Digunakan untuk **escape** string. Meskipun string yang dipakai menggunakan literal \ akan tetap di-escape. + +```go +fmt.Printf("%q\n", `" name \ height "`) +// "\" name \\\\ height \"" +``` + +## A.37.11. Layout Format `%s` + +Digunakan untuk memformat data string. + +```go +fmt.Printf("%s\n", data.name) +// wick +``` + +## A.37.12. Layout Format `%t` + +Digunakan untuk memformat data boolean, menampilkan nilai `bool`-nya. + +```go +fmt.Printf("%t\n", data.isGraduated) +// false +``` + +## A.37.13. Layout Format `%T` + +Berfungsi untuk mengambil tipe variabel yang akan diformat. + +```go +fmt.Printf("%T\n", data.name) +// string + +fmt.Printf("%T\n", data.height) +// float64 + +fmt.Printf("%T\n", data.age) +// int32 + +fmt.Printf("%T\n", data.isGraduated) +// bool + +fmt.Printf("%T\n", data.hobbies) +// []string +``` + +## A.37.14. Layout Format `%v` + +Digunakan untuk memformat data apa saja (termasuk data bertipe `interface{}`). Hasil kembaliannya adalah string nilai data aslinya. + +Jika data adalah objek cetakan `struct`, maka akan ditampilkan semua secara property berurutan. + +```go +fmt.Printf("%v\n", data) +// {wick 182.5 26 false [eating sleeping]} +``` + +## A.37.15. Layout Format `%+v` + +Digunakan untuk memformat struct, mengembalikan nama tiap property dan nilainya berurutan sesuai dengan struktur struct. + +```go +fmt.Printf("%+v\n", data) +// {name:wick height:182.5 age:26 isGraduated:false hobbies:[eating sleeping]} +``` + +## A.37.16. Layout Format `%#v` + +Digunakan untuk memformat struct, mengembalikan nama dan nilai tiap property sesuai dengan struktur struct dan juga bagaimana objek tersebut dideklarasikan. + +```go +fmt.Printf("%#v\n", data) +// main.student{name:"wick", height:182.5, age:26, isGraduated:false, hobbies:[]string{"eating", "sleeping"}} +``` + +Ketika menampilkan objek yang deklarasinya adalah menggunakan teknik *anonymous struct*, maka akan muncul juga struktur anonymous struct nya. + +```go +var data = struct { + name string + height float64 +}{ + name: "wick", + height: 182.5, +} + +fmt.Printf("%#v\n", data) +// struct { name string; height float64 }{name:"wick", height:182.5} +``` + +Format ini juga bisa digunakan untuk menampilkan tipe data lain, dan akan dimunculkan strukturnya juga. + +## A.37.17. Layout Format `%x` atau `%X` + +Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 16 (heksadesimal). + +```go +fmt.Printf("%x\n", data.age) +// 1a +``` + +Jika digunakan pada tipe data string, maka akan mengembalikan kode heksadesimal tiap karakter. + +```go +var d = data.name + +fmt.Printf("%x%x%x%x\n", d[0], d[1], d[2], d[3]) +// 7769636b + +fmt.Printf("%x\n", d) +// 7769636b +``` + +`%x` dan `%X` memiliki fungsi yang sama. Perbedaannya adalah `%X` akan mengembalikan string dalam bentuk *uppercase* atau huruf kapital. + +## A.37.18. Layout Format `%%` + +Cara untuk menulis karakter `%` pada string format. + +```go +fmt.Printf("%%\n") +// % +``` + +--- + + diff --git a/38-time.md b/38-time.md new file mode 100644 index 000000000..f6e092b78 --- /dev/null +++ b/38-time.md @@ -0,0 +1,219 @@ +# A.38. Time, Parsing Time, & Format Time + +Pada bab ini kita akan belajar tentang pemanfaatan data bertipe date-time, method-method yang disediakan, dan juga **format** & **parsing** data `string` ke tipe `time.Time` dan sebaliknya. + +Go menyediakan package `time` yang berisikan banyak sekali komponen yang bisa digunakan untuk keperluan pemanfaatan date-time. Salah satunya adalah `time.Time`, yang merupakan tipe untuk data tanggal dan waktu di Go. + +> Time disini maksudnya adalah gabungan **date** dan **time**, bukan hanya waktu saja. + +## A.38.1. Penggunaan `time.Time` + +Tipe `time.Time` merupakan representasi untuk objek date-time. Ada 2 cara yang bisa dipilih untuk membuat data bertipe ini. + + 1. Menjadikan informasi waktu sekarang sebagai objek `time.Time`, menggunakan `time.Now()`. + 2. Atau, membuat objek baru bertipe `time.Time` dengan informasi ditentukan sendiri, menggunakan `time.Date()`. + +Berikut merupakan contoh penggunannya. + +```go +package main + +import "fmt" +import "time" + +func main() { + var time1 = time.Now() + fmt.Printf("time1 %v\n", time1) + // time1 2015-09-01 17:59:31.73600891 +0700 WIB + + var time2 = time.Date(2011, 12, 24, 10, 20, 0, 0, time.UTC) + fmt.Printf("time2 %v\n", time2) + // time2 2011-12-24 10:20:00 +0000 UTC +} +``` + +Fungsi `time.Now()` mengembalikan objek `time.Time` dengan informasi adalah date-time tepat ketika statement tersebut dijalankan. Bisa dilihat pada saat di-print muncul informasi date-time sesuai dengan tanggal program tersebut dieksekusi. + +![Penggunaan time](images/A.38_1_time_instance.png) + +Fungsi `time.Date()` digunakan untuk membuat objek `time.Time` baru yang informasi date-time-nya kita tentukan sendiri. Fungsi ini memiliki 8 buah parameter *mandatory* dengan skema bisa dilihat di kode berikut: + +```go +time.Date(tahun, bulan, tanggal, jam, menit, detik, nanodetik, timezone) +``` + +Objek cetakan fungsi `time.Now()`, informasi timezone-nya adalah relatif terhadap lokasi kita. Karena kebetulan penulis berlokasi di Jawa Timur, maka akan terdeteksi masuk dalam **GMT+7** atau **WIB**. Berbeda dengan variabel `time2` yang lokasinya sudah kita tentukan secara eksplisit yaitu **UTC**. + +Selain menggunakan `time.UTC` untuk penentuan lokasi, tersedia juga `time.Local` yang nilainya adalah relatif terhadap date-time lokal kita. + +## A.38.2. Method Milik `time.Time` + +Tipe data `time.Time` merupakan struct, memiliki beberapa method yang bisa dipakai. + +```go +var now = time.Now() +fmt.Println("year:", now.Year(), "month:", now.Month()) +// year: 2015 month: 8 +``` + +Kode di atas adalah contoh penggunaan beberapa method milik objek bertipe `time.Time`. Method `Year()` mengembalikan informasi tahun, dan `Month()` mengembalikan informasi angka bulan. + +Selain kedua method di atas, ada banyak lagi yang bisa dimanfaatkan. Tabel berikut merupakan list method yang berhubungan dengan *date*, *time*, dan *location* yang dimiliki tipe `time.Time`. + +| Method | Return Type | Penjelasan | +| :------------ | :--------- | :------- | +| `now.Year()` | `int` | Tahun | +| `now.YearDay()` | `int` | Hari ke-? di mulai awal tahun | +| `now.Month()` | `int` | Bulan | +| `now.Weekday()` | `string` | Nama hari. Bisa menggunakan `now.Weekday().String()` untuk mengambil bentuk string-nya | +| `now.ISOWeek()` | (`int`, `int`) | Tahun dan minggu ke-? mulai awal tahun | +| `now.Day()` | `int` | Tanggal | +| `now.Hour()` | `int` | Jam | +| `now.Minute()` | `int` | Menit | +| `now.Second()` | `int` | Detik | +| `now.Nanosecond()` | `int` | Nano detik | +| `now.Local()` | `time.Time` | Date-time dalam timezone lokal | +| `now.Location()` | `*time.Location` | Mengambil informasi lokasi, apakah *local* atau *utc*. Bisa menggunakan `now.Location().String()` untuk mengambil bentuk string-nya | +| `now.Zone()` | (`string`, `int`) | Mengembalikan informasi *timezone offset* dalam string dan numerik. Sebagai contoh `WIB, 25200` | +| `now.IsZero()` | `bool` | Deteksi apakah nilai object `now` adalah `01 Januari tahun 1, 00:00:00 UTC`. Jika iya maka bernilai `true` | +| `now.UTC()` | `time.Time` | Date-time dalam timezone `UTC` | +| `now.Unix()` | `int64` | Date-time dalam format *unix time* | +| `now.UnixNano()` | `int64` | Date-time dalam format *unix time*. Infomasi nano detik juga dimasukkan | +| `now.String()` | `string` | Date-time dalam string | + +## A.38.3. Parsing dari `string` ke `time.Time` + +Data `string` bisa dikonversi menjadi `time.Time` dengan memanfaatkan `time.Parse`. Fungsi ini membutuhkan 2 parameter: + + - Parameter ke-1 adalah layout format dari data waktu yang akan diparsing. + - Parameter ke-2 adalah data string yang ingin diparsing. + +Contoh penerapannya bisa dilihat di kode berikut. + +```go +var layoutFormat, value string +var date-time.Time + +layoutFormat = "2006-01-02 15:04:05" +value = "2015-09-02 08:04:00" +date, _ = time.Parse(layoutFormat, value) +fmt.Println(value, "\t->", date.String()) +// 2015-09-02 08:04:00 +0000 UTC + +layoutFormat = "02/01/2006 MST" +value = "02/09/2015 WIB" +date, _ = time.Parse(layoutFormat, value) +fmt.Println(value, "\t\t->", date.String()) +// 2015-09-02 00:00:00 +0700 WIB +``` + +![Contoh penggunaan **time.Parse**](images/A.38_2_time_parse.png) + +Layout format date-time di Go berbeda dibanding bahasa lain. Umumnya layout format yang digunakan adalah seperti `"DD/MM/YYYY"`, di Go tidak. + +Go memiliki standar layout format yang cukup unik, contohnya seperti pada kode di atas `"2006-01-02 15:04:05"`. Go menggunakan `2006` untuk parsing tahun, bukan `YYYY`; `01` untuk parsing bulan; `02` untuk parsing hari; dan seterusnya. Detailnya bisa dilihat di tabel berikut. + +| Layout Format | Penjelasan | Contoh Data | +| :-------------- | :---------- | :----- | +| `2006` | Tahun 4 digit | `2015` | +| `006` | Tahun 3 digit | `015` | +| `06` | Tahun 2 digit | `15` | +| `01` | Bulan 2 digit | `05` | +| `1` | Bulan 1 digit jika dibawah bulan 10, selainnya 2 digit | `5`, `12` | +| `January` | Nama bulan dalam bahasa inggris | `September`, `August` | +| `Jan` | Nama bulan dalam bahasa inggris, 3 huruf | `Sep`, `Aug` | +| `02` | Tanggal 2 digit | `02` | +| `2` | Tanggal 1 digit jika dibawah bulan 10, selainnya 2 digit | `8`, `31` | +| `Monday` | Nama hari dalam bahasa inggris | `Saturday`, `Friday` | +| `Mon` | Nama hari dalam bahasa inggris, 3 huruf | `Sat`, `Fri` | +| `15` | Jam dengan format **24 jam** | `18` | +| `03` | Jam dengan format **12 jam** 2 digit | `05`, `11` | +| `3` | Jam dengan format **12 jam** 1 digit jika dibawah jam 11, selainnya 2 digit | `5`, `11` | +| `PM` | AM/PM, biasa digunakan dengan format jam **12 jam** | `PM`, `AM` | +| `04` | Menit 2 digit | `08` | +| `4` | Menit 1 digit jika dibawah menit 10, selainnya 2 digit | `8`, `24` | +| `05` | Detik 2 digit | `06` | +| `5` | Detik 1 digit jika dibawah detik 10, selainnya 2 digit | `6`, `36` | +| `999999` | Nano detik | `124006` | +| `MST` | Lokasi timezone | `UTC`, `WIB`, `EST` | +| `Z0700` | Offset timezone | `Z`, `+0700`, `-0200` | + +## A.38.4. Predefined Layout Format Untuk Keperluan Parsing Time + +Go juga menyediakan beberapa predefined layout format umum yang bisa dimanfaatkan. Jadi tidak perlu menuliskan kombinasi komponen-komponen layout format. + +Salah satu predefined layout yang bisa digunakan adalah `time.RFC822`, ekuivalen dengan layout format `02 Jan 06 15:04 MST`. Berikut adalah contoh penerapannya. + +```go +var date, _ = time.Parse(time.RFC822, "02 Sep 15 08:00 WIB") +fmt.Println(date.String()) +// 2015-09-02 08:00:00 +0700 WIB +``` + +Ada beberapa layout format lain yang tersedia, silakan lihat tabel berikut. + +| Predefined Layout Format | Layout Format | +| :---------------- | :----- | +| `time.ANSIC` | Mon Jan _2 15:04:05 2006 | +| `time.UnixDate` | Mon Jan _2 15:04:05 MST 2006 | +| `time.RubyDate` | Mon Jan 02 15:04:05 -0700 2006 | +| `time.RFC822` | 02 Jan 06 15:04 MST | +| `time.RFC822Z` | 02 Jan 06 15:04 -0700 | +| `time.RFC850` | Monday, 02-Jan-06 15:04:05 MST | +| `time.RFC1123` | Mon, 02 Jan 2006 15:04:05 MST | +| `time.RFC1123Z` | Mon, 02 Jan 2006 15:04:05 -0700 | +| `time.RFC3339` | 2006-01-02T15:04:05Z07:00 | +| `time.RFC3339Nano` | 2006-01-02T15:04:05.999999999Z07:00 | +| `time.Kitchen` | 3:04PM | +| `time.Stamp` | Jan _2 15:04:05 | +| `time.StampMilli` | Jan _2 15:04:05.000 | +| `time.StampMicro` | Jan _2 15:04:05.000000 | +| `time.StampNano` | Jan _2 15:04:05.000000000 | + +## A.38.5. Format dari `time.Time` ke `string` + +Setelah sebelumnya kita belajar tentang cara konversi data dengan tipe `string` ke `time.Time`. Kali ini kita akan belajar kebalikannya, konversi `time.Time` ke `string`. + +Method `Format()` milik tipe `time.Time` digunakan untuk membentuk output `string` sesuai dengan layout format yang diinginkan. Contoh bisa dilihat pada kode berikut. + +```go +var date, _ = time.Parse(time.RFC822, "02 Sep 15 08:00 WIB") + +var dateS1 = date.Format("Monday 02, January 2006 15:04 MST") +fmt.Println("dateS1", dateS1) +// Wednesday 02, September 2015 08:00 WIB + +var dateS2 = date.Format(time.RFC3339) +fmt.Println("dateS2", dateS2) +// 2015-09-02T08:00:00+07:00 +``` + +Variabel `date` di atas berisikan hasil parsing data dengan format `time.RFC822`. Data tersebut kemudian diformat sebagai string 2 kali dengan layout format berbeda. + +![Contoh penggunaan method `Format()`](images/A.38_3_time_format.png) + +## A.38.6. Handle Error Parsing `time.Time` + +Ketika parsing `string` ke `time.Time`, sangat memungkinkan bisa terjadi error karena struktur data yang akan di-parse tidak sesuai layout format yang digunakan. Error tidaknya parsing bisa diketahui lewat nilai kembalian ke-2 fungsi `time.Parse()`. Berikut adalah contoh penerapannya. + +```go +var date, err = time.Parse("06 Jan 15", "02 Sep 15 08:00 WIB") + +if err != nil { + fmt.Println("error", err.Error()) + return +} + +fmt.Println(date) +``` + +Kode di atas menghasilkan error karena format tidak sesuai dengan skema data yang akan diparsing. Layout format yang seharusnya digunakan adalah `06 Jan 15 03:04 MST`. + +![Error parsing time.Time](images/A.38_4_error_parse.png) + +--- + + diff --git a/39-timer.md b/39-timer.md new file mode 100644 index 000000000..faccb90a2 --- /dev/null +++ b/39-timer.md @@ -0,0 +1,202 @@ +# A.39. Timer, Ticker, & Scheduler + +Ada beberapa fungsi dalam package `time` yang bisa dimanfaatkan untuk menunda atau mengatur jadwal eksekusi sebuah proses dalam jeda waktu tertentu. + +## A.39.1. Fungsi `time.Sleep()` + +Fungsi ini digunakan untuk menghentikan program sejenak. `time.Sleep()` bersifat **blocking**, statement dibawahnya tidak akan dieksekusi sampai pemberhentian usai. Contoh sederhana penerapan bisa dilihat pada kode berikut. + +```go +package main + +import "fmt" +import "time" + +func main () { + fmt.Println("start") + time.Sleep(time.Second * 4) + fmt.Println("after 4 seconds") +} +``` + +Hasilnya, tulisan `"start"` muncul, lalu 4 detik kemudian tulisan `"after 4 seconds"` muncul. + +## A.39.2. Scheduler Menggunakan `time.Sleep()` + +Selain untuk blocking proses, fungsi `time.Sleep()` ini bisa dimanfaatkan untuk membuat scheduler sederhana, contohnya seperti berikut, scheduler untuk menampilkan pesan halo setiap 1 detik. + +```go +for true { + fmt.Println("Hello !!") + time.Sleep(1 * time.Second) +} +``` + +## A.39.3. Fungsi `time.NewTimer()` + +Fungsi ini sedikit berbeda dengan `time.Sleep()`. Fungsi `time.NewTimer()` mengembalikan objek bertipe `*time.Timer` yang memiliki property `C` yang bertipe channel. + +Cara kerja fungsi ini: setelah jeda waktu yang ditentukan sebuah data akan dikirimkan lewat channel `C`. Penggunaan fungsi ini harus diikuti dengan statement untuk penerimaan data dari channel `C`. + +Untuk lebih jelasnya silakan perhatikan kode berikut. + +```go +var timer = time.NewTimer(4 * time.Second) +fmt.Println("start") +<-timer.C +fmt.Println("finish") +``` + +Statement `var timer = time.NewTimer(4 * time.Second)` mengindikasikan bahwa nantinya akan ada data yang dikirimkan ke channel `timer.C` setelah 4 detik berlalu. Baris kode `<-timer.C` menandakan penerimaan data dari channel `timer.C`. Karena penerimaan channel sendiri sifatnya adalah blocking, maka statement `fmt.Println("finish")` baru akan dieksekusi setelah **4 detik**. + +Hasil program di atas adalah tulisan `"start"` muncul, lalu setelah 4 detik tulisan `"expired"` muncul. + +## A.39.4. Fungsi `time.AfterFunc()` + +Fungsi `time.AfterFunc()` memiliki 2 parameter. Parameter pertama adalah durasi timer, dan parameter kedua adalah *callback* nya. Callback tersebut akan dieksekusi jika waktu sudah memenuhi durasi timer. + +```go +var ch = make(chan bool) + +time.AfterFunc(4*time.Second, func() { + fmt.Println("expired") + ch <- true +}) + +fmt.Println("start") +<-ch +fmt.Println("finish") +``` + +Hasil dari kode di atas, tulisan `"start"` muncul kemudian setelah 4 detik berlalu, tulisan `"expired"` muncul. + +Didalam callback terdapat proses transfer data lewat channel, menjadikan tulisan `"finish"` akan muncul tepat setelah tulisan `"expired"` muncul. + +Beberapa hal yang perlu diketahui dalam menggunakan fungsi ini: + + - Jika tidak ada serah terima data lewat channel, maka eksekusi `time.AfterFunc()` adalah asynchronous (tidak blocking). + - Jika ada serah terima data lewat channel, maka fungsi akan tetap berjalan asynchronous hingga baris kode dimana penerimaan data channel dilakukan. Proses blocking nya berada pada baris kode penerimaan channel. + +## A.39.5. Fungsi `time.After()` + +Kegunaan fungsi ini mirip seperti `time.Sleep()`. Perbedaannya adalah, fungsi `timer.After()` akan mengembalikan data channel, sehingga perlu menggunakan tanda `<-` dalam penerapannya. + +```go +<-time.After(4 * time.Second) +fmt.Println("expired") +``` + +Tulisan `"expired"` akan muncul setelah 4 detik. + +## A.39.6. Scheduler Menggunakan Ticker + +Selain fungsi-fungsi untuk keperluan timer, Go juga menyediakan fungsi scheduler (yang disini kita sebut sebagai ticker). + +Cara penggunaan ticker cukup mudah, buat objek ticker baru menggunakan `time.NewTicker()` isi argument dengan durasi yang diinginkan. Dari objek tersebut kita bisa akses properti `.C` yang merupakan channel. Setiap durasi yang sudah ditentikan, objek ticker akan mengirimkan informasi date-time via channel tersebut. + +```go +package main + +import ( + "fmt" + "time" +) + +func main() { + done := make(chan bool) + ticker := time.NewTicker(time.Second) + + go func() { + time.Sleep(10 * time.Second) // wait for 10 seconds + done <- true + }() + + for { + select { + case <-done: + ticker.Stop() + return + case t := <-ticker.C: + fmt.Println("Hello !!", t) + } + } +} + +``` + +Pada contoh di atas bisa dilihat, selain ticker disiapkan juga variabel channel `done`. Variabel ini kita gunakan untuk mengontrol kapan ticker harus di stop. + +Cara kerja program di atas: teknik `for` - `select` pada channel digunakan untuk mengecek penerimaan data dari channel `done` dan `ticker.C`. By default, channel `ticker.C` akan menerima kiriman data setiap N duration yang dimana pada kode di atas adalah 1 detik (lihat argumen inisialisasi objek ticker). + +Data yang dikirimkan via channel `ticker.C` adalah data date-time kapan event itu terjadi. Pada kode di atas, setiap ada kiriman data via channel tersebut kita tampilkan. + +Sebelum blok kode perulangan `for`, bisa kita lihat ada goroutine baru di-dispatch, isinya adalah mengirim data ke channel `done` setelah 10 detik. Data tersebut nantinya akan diterima oleh blok kode `for` - `select`, dan ketika itu terjadi, method `.Stop()` milik objek ticker dipanggil untuk menonaktifkan scheduler pada ticker tersebut. + +Jadi, selama 10 detik, di setiap detiknya akan muncul pesan halo. + +## A.39.7. Kombinasi Timer & Goroutine + +Berikut merupakan contoh penerapan timer dan goroutine. Program di bawah ini adalah program tanya-jawab sederhana. Sebuah pertanyaan muncul dan user harus menginputkan jawaban dalam waktu tidak lebih dari 5 detik. Jika 5 detik berlalu dan belum ada jawaban, maka akan muncul pesan *time out*. + +OK langsung saja, mari kita buat programnya, pertama, import package yang diperlukan. + +```go +package main + +import "fmt" +import "os" +import "time" +``` + +Buat fungsi `timer()`, nantinya fungsi ini dieksekusi sebagai goroutine. Di dalam fungsi `timer()` terdapat blok kode jika waktu sudah mencapai `timeout`, maka sebuah data dikirimkan lewat channel `ch`. + +```go +func timer(timeout int, ch chan<- bool) { + time.AfterFunc(time.Duration(timeout)*time.Second, func() { + ch <- true + }) +} +``` + +Siapkan juga fungsi `watcher()`. Fungsi ini juga akan dieksekusi sebagai goroutine. Tugasnya cukup sederhana, yaitu menerima data dari channel `ch` (jika ada penerimaan data, berarti sudah masuk waktu timeout), lalu menampilkan pesan bahwa waktu telah habis. + +```go +func watcher(timeout int, ch <-chan bool) { + <-ch + fmt.Println("\ntime out! no answer more than", timeout, "seconds") + os.Exit(0) +} +``` + +Terakhir, buat implementasi di fungsi `main()`. + +```go +func main() { + var timeout = 5 + var ch = make(chan bool) + + go timer(timeout, ch) + go watcher(timeout, ch) + + var input string + fmt.Print("what is 725/25 ? ") + fmt.Scan(&input) + + if input == "29" { + fmt.Println("the answer is right!") + } else { + fmt.Println("the answer is wrong!") + } +} +``` + +Ketika user tidak menginputkan apa-apa dalam kurun waktu 5 detik, maka akan muncul pesan timeout, lalu program dihentikan. + +![Penerapan timer dalam goroutine](images/A.39_1_timer.png) + +--- + + diff --git a/4-instalasi-editor.md b/4-instalasi-editor.md new file mode 100644 index 000000000..266d9e862 --- /dev/null +++ b/4-instalasi-editor.md @@ -0,0 +1,23 @@ +# A.4. Instalasi Editor + +Proses pembuatan aplikasi menggunakan Go akan lebih maksimal jika didukung oleh editor atau **IDE** yang pas. Ada cukup banyak pilihan bagus yang bisa dipertimbangkan, diantaranya: Brackets, JetBrains GoLand, Netbeans, Atom, Brackets, Visual Studio Code, Sublime Text, dan lainnya. + +Penulis sarankan untuk memilih editor yang paling nyaman digunakan, preferensi masing-masing pastinya berbeda. Penulis sendiri sempat menggunakan Sublime Text 3, tapi sekarang pindah ke **Visual Studio Code**. Editor ini sangat ringan, mudah didapat, dan memiliki ekstensi yang bagus untuk bahasa Go. Jika pembaca ingin menggunakan editor yang sama, maka silakan melanjutkan guide berikut. + +Di bab ini akan dijelaskan bagaimana cara instalasi editor Visual Studio Code. + +## A.4.1. Instalasi Editor Visual Studio Code + + 1. Download Visual Studio Code di [https://code.visualstudio.com/Download](https://code.visualstudio.com/Download), pilih sesuai dengan sistem operasi yang digunakan. + 2. Jalankan installer. + 3. Setelah selesai, jalankan aplikasi. + +![Tampilan Visual Studio Code](images/A.4_1_visual_studio_code.png) + +## A.4.2. Instalasi Extensi Go + +Dengan meng-instal Go Extension, maka programming menggunakan bahasa ini akan menjadi sangat nyaman lewat VS Code. Banyak benefit yang didapat dengan meng-install ekstensi ini, beberapa diantaranya adalah integrasi dengan kompiler Go, auto lint on save, testing with coverage, fasilitas debugging with breakpoints, dan lainnya. + +Cara instalasi ekstensi sendiri cukup mudah, klik `View -> Extension` atau klik ikon *Extension Marketplace* di sebelah kiri (silakan lihat gambar berikut, deretan button paling kiri yang dilingkari merah). Setelah itu ketikkan **Go** pada inputan search, silakan install ekstensi Go buatan Microsoft, biasanya muncul paling atas sendiri. + +![VSCode Go extension](images/A.4_2_vscode_go_extension.png) diff --git a/40-data-type-conversion.md b/40-data-type-conversion.md new file mode 100644 index 000000000..bfd82d58e --- /dev/null +++ b/40-data-type-conversion.md @@ -0,0 +1,250 @@ +# A.40. Konversi Antar Tipe Data + +Di bab-bab sebelumnya kita sudah mengaplikasikan beberapa cara konversi data, contohnya seperti konversi `string` ↔ `int` menggunakan `strconv`, dan `time.Time` ↔ `string`. Di bab ini kita akan belajar lebih banyak. + +## A.40.1. Konversi Menggunakan `strconv` + +Package `strconv` berisi banyak fungsi yang sangat membantu kita untuk melakukan konversi. Berikut merupakan beberapa fungsi yang dalam package tersebut. + +### A.40.1.1. Fungsi `strconv.Atoi()` + +Fungsi ini digunakan untuk konversi data dari tipe `string` ke `int`. `strconv.Atoi()` menghasilkan 2 buah nilai kembalian, yaitu hasil konversi dan `error` (jika konversi sukses, maka `error` berisi `nil`). + +```go +package main + +import "fmt" +import "strconv" + +func main() { + var str = "124" + var num, err = strconv.Atoi(str) + + if err == nil { + fmt.Println(num) // 124 + } +} +``` + +### A.40.1.2. Fungsi `strconv.Itoa()` + +Merupakan kebalikan dari `strconv.Atoi`, berguna untuk konversi `int` ke `string`. + +```go +var num = 124 +var str = strconv.Itoa(num) + +fmt.Println(str) // "124" +``` + +### A.40.1.3. Fungsi `strconv.ParseInt()` + +Digunakan untuk konversi `string` berbentuk numerik dengan basis tertentu ke tipe numerik non-desimal dengan lebar data bisa ditentukan. + +Pada contoh berikut, string `"124"` dikonversi ke tipe numerik dengan ketentuan basis yang digunakan `10` dan lebar datanya mengikuti tipe `int64` (lihat parameter ketiga). + +```go +var str = "124" +var num, err = strconv.ParseInt(str, 10, 64) + +if err == nil { + fmt.Println(num) // 124 +} +``` + +Contoh lainnya, string `"1010"` dikonversi ke basis 2 (biner) dengan tipe data hasil adalah `int8`. + +```go +var str = "1010" +var num, err = strconv.ParseInt(str, 2, 8) + +if err == nil { + fmt.Println(num) // 10 +} +``` + +### A.40.1.4. Fungsi `strconv.FormatInt()` + +Berguna untuk konversi data numerik `int64` ke `string` dengan basis numerik bisa ditentukan sendiri. + +```go +var num = int64(24) +var str = strconv.FormatInt(num, 8) + +fmt.Println(str) // 30 +``` + +### A.40.1.5. Fungsi `strconv.ParseFloat()` + +Digunakan untuk konversi `string` ke numerik desimal dengan lebar data bisa ditentukan. + +```go +var str = "24.12" +var num, err = strconv.ParseFloat(str, 32) + +if err == nil { + fmt.Println(num) // 24.1200008392334 +} +``` + +Pada contoh di atas, string `"24.12"` dikonversi ke float dengan lebar tipe data `float32`. Hasil konversi `strconv.ParseFloat` adalah sesuai dengan standar [IEEE Standard for Floating-Point Arithmetic](https://en.wikipedia.org/wiki/IEEE_floating_point). + +### A.40.1.6. Fungsi `strconv.FormatFloat()` + +Berguna untuk konversi data bertipe `float64` ke `string` dengan format eksponen, lebar digit desimal, dan lebar tipe data bisa ditentukan. + +```go +var num = float64(24.12) +var str = strconv.FormatFloat(num, 'f', 6, 64) + +fmt.Println(str) // 24.120000 +``` + +Pada kode di atas, Data `24.12` yang bertipe `float64` dikonversi ke string dengan format eksponen `f` atau tanpa eksponen, lebar digit desimal 6 digit, dan lebar tipe data `float64`. + +Ada beberapa format eksponen yang bisa digunakan. Detailnya bisa dilihat di tabel berikut. + +| Format Eksponen | Penjelasan | +| :-------------: | :-------- | +| `b` | -ddddp±ddd, a, eksponen biner (basis 2) | +| `e` | -d.dddde±dd, a, eksponen desimal (basis 10) | +| `E` | -d.ddddE±dd, a, eksponen desimal (basis 10) | +| `f` | -ddd.dddd, tanpa eksponen | +| `g` | Akan menggunakan format eksponen `e` untuk eksponen besar dan `f` untuk selainnya | +| `G` | Akan menggunakan format eksponen `E` untuk eksponen besar dan `f` untuk selainnya | + +### A.40.1.7. Fungsi `strconv.ParseBool()` + +Digunakan untuk konversi `string` ke `bool`. + +```go +var str = "true" +var bul, err = strconv.ParseBool(str) + +if err == nil { + fmt.Println(bul) // true +} +``` + +### A.40.1.8. Fungsi `strconv.FormatBool()` + +Digunakan untuk konversi `bool` ke `string`. + +```go +var bul = true +var str = strconv.FormatBool(bul) + +fmt.Println(str) // 124 +``` + +## A.40.2. Konversi Data Menggunakan Teknik Casting + +Keyword tipe data bisa digunakan untuk casting, atau konversi antar tipe data. Cara penggunaannya adalah dengan menuliskan tipe data tujuan casting sebagai fungsi, lalu menyisipkan data yang akan dikonversi sebagai parameter fungsi tersebut. + +```go +var a float64 = float64(24) +fmt.Println(a) // 24 + +var b int32 = int32(24.00) +fmt.Println(b) // 24 +``` + +## A.40.3. Casting `string` ↔ `byte` + +String sebenarnya adalah slice/array `byte`. Di Go sebuah karakter biasa (bukan unicode) direpresentasikan oleh sebuah elemen slice byte. Tiap elemen slice berisi data `int` dengan basis desimal, yang merupakan kode ASCII dari karakter dalam string. + +Cara mendapatkan slice byte dari sebuah data string adalah dengan meng-casting-nya ke tipe `[]byte`. + +```go +var text1 = "halo" +var b = []byte(text1) + +fmt.Printf("%d %d %d %d \n", b[0], b[1], b[2], b[3]) +// 104 97 108 111 +``` + +Pada contoh di atas, string dalam variabel `text1` dikonversi ke `[]byte`. Tiap elemen slice byte tersebut kemudian ditampilkan satu-per-satu. + +Contoh berikut ini merupakan kebalikan dari contoh di atas, data bertipe `[]byte` akan dicari bentuk `string`-nya. + +```go +var byte1 = []byte{104, 97, 108, 111} +var s = string(byte1) + +fmt.Printf("%s \n", s) +// halo +``` + +Pada contoh di-atas, beberapa kode byte dituliskan dalam bentuk slice, ditampung variabel `byte1`. Lalu, nilai variabel tersebut di-cast ke `string`, untuk kemudian ditampilkan. + +Selain itu, tiap karakter string juga bisa di-casting ke bentuk `int`, hasilnya adalah sama yaitu data byte dalam bentuk numerik basis desimal, dengan ketentuan literal string yang digunakan adalah tanda petik satu ('). + +Juga berlaku sebaliknya, data numerik jika di-casting ke bentuk string dideteksi sebagai kode ASCII dari karakter yang akan dihasilkan. + +```go +var c int64 = int64('h') +fmt.Println(c) // 104 + +var d string = string(104) +fmt.Println(d) // h +``` + +## A.40.4. Type Assertions Pada Interface Kosong (`interface{}`) + +**Type assertions** merupakan teknik untuk mengambil tipe data konkret dari data yang terbungkus dalam `interface{}`. Jadi bisa disimpulkan bahwa teknik type assertions hanya bisa dilakukan pada data bertipe `interface{}`. Lebih jelasnya silakan cek contoh berikut. + +Variabel `data` disiapkan bertipe `map[string]interface{}`, map tersebut berisikan beberapa item dengan tipe data value-nya berbeda satu sama lain, sementara tipe data untuk key-nya sama yaitu `string`. + +```go +var data = map[string]interface{}{ + "nama": "john wick", + "grade": 2, + "height": 156.5, + "isMale": true, + "hobbies": []string{"eating", "sleeping"}, +} + +fmt.Println(data["nama"].(string)) +fmt.Println(data["grade"].(int)) +fmt.Println(data["height"].(float64)) +fmt.Println(data["isMale"].(bool)) +fmt.Println(data["hobbies"].([]string)) +``` + +Statement `data["nama"].(string)` maksudnya adalah, nilai `data["nama"]` yang bertipe `interface{}` diambil nilai konkretnya dalam bentuk string `string`. + +Pada kode di atas, tidak akan terjadi panic error, karena semua operasi type assertion adalah dilakukan menggunakan tipe data yang sudah sesuai dengan tipe data nilai aslinya. Seperti `data["nama"]` yang merupakan `string` pasti bisa di-asertasi ke tipe `string`. + +Coba lakukan asertasi ke tipe yang tidak sesuai dengan tipe nilai aslinya, seperti `data["nama"].(int)`, pasti akan men-trigger panic error. + +Nah, dari penjelasan di atas, terlihat bahwa kita harus tau terlebih dahulu apa tipe data asli dari data yang tersimpan dalam interface. Jika misal tidak tau, maka bisa gunakan teknik di bawah ini untuk pengecekan sukses tidaknya proses asertasi. + +Tipe asli data pada variabel `interface{}` bisa diketahui dengan cara meng-casting ke tipe `type`, namun casting ini hanya bisa dilakukan pada `switch`. + +```go +for _, val := range data { + switch val.(type) { + case string: + fmt.Println(val.(string)) + case int: + fmt.Println(val.(int)) + case float64: + fmt.Println(val.(float64)) + case bool: + fmt.Println(val.(bool)) + case []string: + fmt.Println(val.([]string)) + default: + fmt.Println(val.(int)) + } +} +``` + +Kombinasi `switch` - `case` bisa dimanfaatkan untuk deteksi tipe konkret data yang bertipe `interface{}`, contoh penerapannya seperti pada kode di atas. + +--- + + diff --git a/41-strings.md b/41-strings.md new file mode 100644 index 000000000..9acef57a1 --- /dev/null +++ b/41-strings.md @@ -0,0 +1,163 @@ +# A.41. Fungsi String + +Go menyediakan package `strings`, isinya banyak fungsi untuk keperluan pengolahan data string. Bab ini berisi pembahasan mengenai beberapa fungsi yang ada di dalam package tersebut. + +## A.41.1. Fungsi `strings.Contains()` + +Dipakai untuk deteksi apakah string (parameter kedua) merupakan bagian dari string lain (parameter pertama). Nilai kembaliannya berupa `bool`. + +```go +package main + +import "fmt" +import "strings" + +func main() { + var isExists = strings.Contains("john wick", "wick") + fmt.Println(isExists) +} +``` + +Variabel `isExists` akan bernilai `true`, karena string `"wick"` merupakan bagian dari `"john wick"`. + +## A.41.2. Fungsi `strings.HasPrefix()` + +Digunakan untuk deteksi apakah sebuah string (parameter pertama) diawali string tertentu (parameter kedua). + +```go +var isPrefix1 = strings.HasPrefix("john wick", "jo") +fmt.Println(isPrefix1) // true + +var isPrefix2 = strings.HasPrefix("john wick", "wi") +fmt.Println(isPrefix2) // false +``` + +## A.41.3. Fungsi `strings.HasSuffix()` + +Digunakan untuk deteksi apakah sebuah string (parameter pertama) diakhiri string tertentu (parameter kedua). + +```go +var isSuffix1 = strings.HasSuffix("john wick", "ic") +fmt.Println(isSuffix1) // false + +var isSuffix2 = strings.HasSuffix("john wick", "ck") +fmt.Println(isSuffix2) // true +``` + +## A.41.4. Fungsi `strings.Count()` + +Memiliki kegunaan untuk menghitung jumlah karakter tertentu (parameter kedua) dari sebuah string (parameter pertama). Nilai kembalian fungsi ini adalah jumlah karakternya. + +```go +var howMany = strings.Count("ethan hunt", "t") +fmt.Println(howMany) // 2 +``` + +Nilai yang dikembalikan `2`, karena pada string `"ethan hunt"` terdapat dua buah karakter `"t"`. + +## A.41.5. Fungsi `strings.Index()` + +Digunakan untuk mencari posisi indeks sebuah string (parameter kedua) dalam string (parameter pertama). + +```go +var index1 = strings.Index("ethan hunt", "ha") +fmt.Println(index1) // 2 +``` + +String `"ha"` berada pada posisi ke `2` dalam string `"ethan hunt"` (indeks dimulai dari 0). Jika diketemukan dua substring, maka yang diambil adalah yang pertama, contoh: + +```go +var index2 = strings.Index("ethan hunt", "n") +fmt.Println(index2) // 4 +``` + +String `"n"` berada pada indeks `4` dan `8`. Yang dikembalikan adalah yang paling kiri (paling kecil), yaitu `4`. + +## A.41.6. Fungsi `strings.Replace()` + +Fungsi ini digunakan untuk replace atau mengganti bagian dari string dengan string tertentu. Jumlah substring yang di-replace bisa ditentukan, apakah hanya 1 string pertama, 2 string, atau kesemuanya. + +```go +var text = "banana" +var find = "a" +var replaceWith = "o" + +var newText1 = strings.Replace(text, find, replaceWith, 1) +fmt.Println(newText1) // "bonana" + +var newText2 = strings.Replace(text, find, replaceWith, 2) +fmt.Println(newText2) // "bonona" + +var newText3 = strings.Replace(text, find, replaceWith, -1) +fmt.Println(newText3) // "bonono" +``` + +Penjelasan: + + 1. Pada contoh di atas, substring `"a"` pada string `"banana"` akan di-replace dengan string `"o"`. + 2. Pada `newText1`, hanya 1 huruf `o` saja yang tereplace karena maksimal substring yang ingin di-replace ditentukan 1. + 3. Angka `-1` akan menjadikan proses replace berlaku pada semua substring. Contoh bisa dilihat pada `newText3`. + +## A.41.7. Fungsi `strings.Repeat()` + +Digunakan untuk mengulang string (parameter pertama) sebanyak data yang ditentukan (parameter kedua). + +```go +var str = strings.Repeat("na", 4) +fmt.Println(str) // "nananana" +``` + +Pada contoh di atas, string `"na"` diulang sebanyak 4 kali. Hasilnya adalah: `"nananana"` + +## A.41.8. Fungsi `strings.Split()` + +Digunakan untuk memisah string (parameter pertama) dengan tanda pemisah bisa ditentukan sendiri (parameter kedua). Hasilnya berupa array string. + +```go +var string1 = strings.Split("the dark knight", " ") +fmt.Println(string1) // ["the", "dark", "knight"] + +var string2 = strings.Split("batman", "") +fmt.Println(string2) // ["b", "a", "t", "m", "a", "n"] +``` + +String `"the dark knight"` dipisah oleh karakter spasi `" "`, hasilnya kemudian ditampung oleh `string1`. + +Untuk memisah string menjadi array tiap 1 string, gunakan pemisah string kosong `""`. Bisa dilihat contohnya pada variabel `string2`. + +## A.41.9. Fungsi `strings.Join()` + +Memiliki kegunaan berkebalikan dengan `strings.Split()`. Digunakan untuk menggabungkan array string (parameter pertama) menjadi sebuah string dengan pemisah tertentu (parameter kedua. + +```go +var data = []string{"banana", "papaya", "tomato"} +var str = strings.Join(data, "-") +fmt.Println(str) // "banana-papaya-tomato" +``` + +Array `data` digabungkan menjadi satu dengan pemisah tanda *dash* (`-`). + +## A.41.10. Fungsi `strings.ToLower()` + +Mengubah huruf-huruf string menjadi huruf kecil. + +```go +var str = strings.ToLower("aLAy") +fmt.Println(str) // "alay" +``` + +## A.41.11. Fungsi `strings.ToUpper()` + +Mengubah huruf-huruf string menjadi huruf besar. + +```go +var str = strings.ToUpper("eat!") +fmt.Println(str) // "EAT!" +``` + +--- + + diff --git a/42-regex.md b/42-regex.md new file mode 100644 index 000000000..4d642256a --- /dev/null +++ b/42-regex.md @@ -0,0 +1,168 @@ +# A.42. Regex + +Regex atau regexp atau **regular expression** adalah suatu teknik yang digunakan untuk pencocokan string dengan pola tertentu. Regex biasa dimanfaatkan untuk pencarian dan pengubahan data string. + +Go mengadopsi standar regex **RE2**, untuk melihat sintaks yang di-support engine ini bisa langsung merujuk ke dokumentasinya di [https://github.com/google/re2/wiki/Syntax](https://github.com/google/re2/wiki/Syntax). + +Pada bab ini kita akan belajar mengenai pengaplikasian regex dengan memanfaatkan fungsi-fungsi dalam package `regexp`. + +## A.42.1. Penerapan Regexp + +Fungsi `regexp.Compile()` digunakan untuk mengkompilasi ekspresi regex. Fungsi tersebut mengembalikan objek bertipe `regexp.*Regexp`. + +Berikut merupakan contoh penerapan regex untuk pencarian karakter. + +```go +package main + +import "fmt" +import "regexp" + +func main() { + var text = "banana burger soup" + var regex, err = regexp.Compile(`[a-z]+`) + + if err != nil { + fmt.Println(err.Error()) + } + + var res1 = regex.FindAllString(text, 2) + fmt.Printf("%#v \n", res1) + // ["banana", "burger"] + + var res2 = regex.FindAllString(text, -1) + fmt.Printf("%#v \n", res2) + // ["banana", "burger", "soup"] +} +``` + +Ekspresi `[a-z]+` maknanya adalah, semua string yang merupakan alphabet yang hurufnya kecil. Ekspresi tersebut di-compile oleh `regexp.Compile()` lalu disimpan ke variabel objek `regex` bertipe `regexp.*Regexp`. + +Struct `regexp.Regexp` memiliki banyak method, salah satunya adalah `FindAllString()`, berfungsi untuk mencari semua string yang sesuai dengan ekspresi regex, dengan kembalian berupa array string. + +Jumlah hasil pencarian dari `regex.FindAllString()` bisa ditentukan. Contohnya pada `res1`, ditentukan maksimal `2` data saja pada nilai kembalian. Jika batas di set `-1`, maka akan mengembalikan semua data. + +Ada cukup banyak method struct `regexp.*Regexp` yang bisa kita manfaatkan untuk keperluan pengelolaan string. Berikut merupakan pembahasan tiap method-nya. + +## A.42.2. Method `MatchString()` + +Method ini digunakan untuk mendeteksi apakah string memenuhi sebuah pola regexp. + +```go +var text = "banana burger soup" +var regex, _ = regexp.Compile(`[a-z]+`) + +var isMatch = regex.MatchString(text) +fmt.Println(isMatch) +// true +``` + +Pada contoh di atas `isMatch` bernilai `true` karena string `"banana burger soup"` memenuhi pola regex `[a-z]+`. + +## A.42.3. Method `FindString()` + +Digunakan untuk mencari string yang memenuhi kriteria regexp yang telah ditentukan. + +```go +var text = "banana burger soup" +var regex, _ = regexp.Compile(`[a-z]+`) + +var str = regex.FindString(text) +fmt.Println(str) +// "banana" +``` + +Fungsi ini hanya mengembalikan 1 buah hasil saja. Jika ada banyak substring yang sesuai dengan ekspresi regexp, akan dikembalikan yang pertama saja. + +## A.42.4. Method `FindStringIndex()` + +Digunakan untuk mencari index string kembalian hasil dari operasi regexp. + +```go +var text = "banana burger soup" +var regex, _ = regexp.Compile(`[a-z]+`) + +var idx = regex.FindStringIndex(text) +fmt.Println(idx) +// [0, 6] + +var str = text[0:6] +fmt.Println(str) +// "banana" +``` + +Method ini sama dengan `FindString()` hanya saja yang dikembalikan indeks-nya. + +## A.42.5. Method `FindAllString()` + +Digunakan untuk mencari banyak string yang memenuhi kriteria regexp yang telah ditentukan. + +```go +var text = "banana burger soup" +var regex, _ = regexp.Compile(`[a-z]+`) + +var str1 = regex.FindAllString(text, -1) +fmt.Println(str1) +// ["banana", "burger", "soup"] + +var str2 = regex.FindAllString(text, 1) +fmt.Println(str2) +// ["banana"] +``` + +Jumlah data yang dikembalikan bisa ditentukan. Jika diisi dengan `-1`, maka akan mengembalikan semua data. + +## A.42.6. Method `ReplaceAllString()` + +Berguna untuk me-replace semua string yang memenuhi kriteri regexp, dengan string lain. + +```go +var text = "banana burger soup" +var regex, _ = regexp.Compile(`[a-z]+`) + +var str = regex.ReplaceAllString(text, "potato") +fmt.Println(str) +// "potato potato potato" +``` + +## A.42.7. Method `ReplaceAllStringFunc()` + +Digunakan untuk me-replace semua string yang memenuhi kriteri regexp, dengan kondisi yang bisa ditentukan untuk setiap substring yang akan di replace. + +```go +var text = "banana burger soup" +var regex, _ = regexp.Compile(`[a-z]+`) + +var str = regex.ReplaceAllStringFunc(text, func(each string) string { + if each == "burger" { + return "potato" + } + return each +}) +fmt.Println(str) +// "banana potato soup" +``` + +Pada contoh di atas, jika salah satu substring yang *match* adalah `"burger"` maka akan diganti dengan `"potato"`, string selainnya tidak di replace. + +## A.42.8. Method `Split()` + +Digunakan untuk memisah string dengan pemisah adalah substring yang memenuhi kriteria regexp yang telah ditentukan. + +Jumlah karakter yang akan di split bisa ditentukan dengan mengisi parameter kedua fungsi `regex.Split()`. Jika di-isi `-1` maka semua karakter yang memenuhi regex akan di-replace. Contoh lain, jika di-isi `2`, maka hanya 2 karakter pertama yang memenuhi regex akan di-replace. + +```go +var text = "banana,burger,soup" +var regex, _ = regexp.Compile(`[a-z]+`) + +var str = regex.Split(text, -1) +fmt.Printf("%#v \n", str) +// ["", ",", ",", ""] +``` + +--- + + diff --git a/43-encoding-base64.md b/43-encoding-base64.md new file mode 100644 index 000000000..68a2c4632 --- /dev/null +++ b/43-encoding-base64.md @@ -0,0 +1,88 @@ +# A.43. Encode - Decode Base64 + +Go memiliki package `encoding/base64`, berisikan fungsi-fungsi untuk kebutuhan **encode** dan **decode** data ke base64 dan sebaliknya. Data yang akan di-encode harus bertipe `[]byte`, perlu dilakukan casting untuk data-data yang belum sesuai tipenya. + +Ada beberapa cara yang bisa digunakan untuk encode dan decode data, dan di bab ini kita akan mempelajarinya. + +## A.43.1. Penerapan Fungsi `EncodeToString()` & `DecodeString()` + +Fungsi `EncodeToString()` digunakan untuk encode data dari bentuk string ke base46. Fungsi `DecodeString()` melakukan kebalikan dari `EncodeToString()`. Berikut adalah contoh penerapannya. + +```go +package main + +import "encoding/base64" +import "fmt" + +func main() { + var data = "john wick" + + var encodedString = base64.StdEncoding.EncodeToString([]byte(data)) + fmt.Println("encoded:", encodedString) + + var decodedByte, _ = base64.StdEncoding.DecodeString(encodedString) + var decodedString = string(decodedByte) + fmt.Println("decoded:", decodedString) +} +``` + +Variabel `data` yang bertipe `string`, harus di-casting terlebih dahulu kedalam bentuk `[]byte` sebelum di-encode menggunakan fungsi `base64.StdEncoding.EncodeToString()`. Hasil encode adalah data base64 bertipe `string`. + +Sedangkan pada fungsi decode `base64.StdEncoding.DecodeString()`, data base64 bertipe `string` di-decode kembali ke string aslinya, tapi bertipe `[]byte`. Ekspresi `string(decodedByte)` menjadikan data `[]byte` tersebut berubah menjadi string. + +![Encode & decode data string](images/A.43_1_encode_decode.png) + +## A.43.2. Penerapan Fungsi `Encode()` & `Decode()` + +Kedua fungsi ini kegunaannya sama dengan fungsi yang sebelumnya kita bahas, salah satu pembedanya adalah data yang akan dikonversi dan hasilnya bertipe `[]byte`. Penggunaan cara ini cukup panjang karena variabel penyimpan hasil encode maupun decode harus disiapkan terlebih dahulu, dan harus memiliki lebar data sesuai dengan hasil yang akan ditampung (yang nilainya bisa dicari menggunakan fungsi `EncodedLen()` dan `DecodedLen()`). + +Lebih jelasnya silakan perhatikan contoh berikut. + +```go +var data = "john wick" + +var encoded = make([]byte, base64.StdEncoding.EncodedLen(len(data))) +base64.StdEncoding.Encode(encoded, []byte(data)) +var encodedString = string(encoded) +fmt.Println(encodedString) + +var decoded = make([]byte, base64.StdEncoding.DecodedLen(len(encoded))) +var _, err = base64.StdEncoding.Decode(decoded, encoded) +if err != nil { + fmt.Println(err.Error()) +} +var decodedString = string(decoded) +fmt.Println(decodedString) +``` + +Fungsi `base64.StdEncoding.EncodedLen(len(data))` menghasilkan informasi lebar data-ketika-sudah-di-encode. Nilai tersebut kemudian ditentukan sebagai lebar alokasi tipe `[]byte` pada variabel `encoded` yang nantinya digunakan untuk menampung hasil encoding. + +Fungsi `base64.StdEncoding.DecodedLen()` memiliki kegunaan sama dengan `EncodedLen()`, hanya saja digunakan untuk keperluan decoding. + +Dibanding 2 fungsi sebelumnya, fungsi `Encode()` dan `Decode()` memiliki beberapa perbedaan. Selain lebar data penampung encode/decode harus dicari terlebih dahulu, terdapat perbedaan lainnya, yaitu pada fungsi ini hasil encode/decode tidak didapat dari nilai kembalian, melainkan dari parameter. Variabel yang digunakan untuk menampung hasil, disisipkan pada parameter fungsi tersebut. + +Pada pemanggilan fungsi encode/decode, variabel `encoded` dan `decoded` tidak disisipkan nilai pointer-nya, cukup di-pass dengan cara biasa, tipe datanya sudah dalam bentuk `[]byte`. + +## A.43.3. Encode & Decode Data URL + +Khusus encode data string yang isinya merupakan URL, lebih efektif menggunakan `URLEncoding` dibandingkan `StdEncoding`. + +Cara penerapannya kurang lebih sama, bisa menggunakan metode pertama maupun metode kedua yang sudah dibahas di atas. Cukup ganti `StdEncoding` menjadi `URLEncoding`. + +```go +var data = "https://kalipare.com/" + +var encodedString = base64.URLEncoding.EncodeToString([]byte(data)) +fmt.Println(encodedString) + +var decodedByte, _ = base64.URLEncoding.DecodeString(encodedString) +var decodedString = string(decodedByte) +fmt.Println(decodedString) +``` + +--- + + diff --git a/44-hash-sha1.md b/44-hash-sha1.md new file mode 100644 index 000000000..535daf3ce --- /dev/null +++ b/44-hash-sha1.md @@ -0,0 +1,103 @@ +# A.44. Hash SHA1 + +Hash adalah algoritma enkripsi untuk mengubah text menjadi deretan karakter acak. Jumlah karakter hasil hash selalu sama. Hash termasuk *one-way encription*, membuat hasil dari hash tidak bisa dikembalikan ke text asli. + +SHA1 atau **Secure Hash Algorithm 1** merupakan salah satu algoritma hashing yang sering digunakan untuk enkripsi data. Hasil dari sha1 adalah data dengan lebar **20 byte** atau **160 bit**, biasa ditampilkan dalam bentuk bilangan heksadesimal 40 digit. + +Di bab ini kita akan belajar tentang pemanfaatan sha1 dan teknik salting dalam hash. + +## A.44.1. Penerapan Hash SHA1 + +Go menyediakan package `crypto/sha1`, berisikan library untuk keperluan *hashing*. Cara penerapannya cukup mudah, contohnya bisa dilihat pada kode berikut. + +```go +package main + +import "crypto/sha1" +import "fmt" + +func main() { + var text = "this is secret" + var sha = sha1.New() + sha.Write([]byte(text)) + var encrypted = sha.Sum(nil) + var encryptedString = fmt.Sprintf("%x", encrypted) + + fmt.Println(encryptedString) + // f4ebfd7a42d9a43a536e2bed9ee4974abf8f8dc8 +} +``` + +Variabel hasil dari `sha1.New()` adalah objek bertipe `hash.Hash`, memiliki dua buah method `Write()` dan `Sum()`. + + - Method `Write()` digunakan untuk menge-set data yang akan di-hash. Data harus dalam bentuk `[]byte`. + - Method `Sum()` digunakan untuk eksekusi proses hash, menghasilkan data yang sudah di-hash dalam bentuk `[]byte`. Method ini membutuhkan sebuah parameter, isi dengan nil. + +Untuk mengambil bentuk heksadesimal string dari data yang sudah di-hash, bisa memanfaatkan fungsi `fmt.Sprintf` dengan layout format `%x`. + +![Hashing menggunakan SHA1](images/A.44_1_hash_sha1.png) + +## A.44.2. Metode Salting Pada Hash SHA1 + +Salt dalam konteks kriptografi adalah data acak yang digabungkan pada data asli sebelum proses hash dilakukan. + +Hash merupakan enkripsi satu arah dengan lebar data yang sudah pasti, sangat mungkin sekali kalau hasil hash untuk beberapa data adalah sama. Disinilah kegunaan **salt**, teknik ini berguna untuk mencegah serangan menggunakan metode pencocokan data-data yang hasil hash-nya adalah sama *(dictionary attack)*. + +Langsung saja kita praktekan. Pertama import package yang dibutuhkan. Lalu buat fungsi untuk hash menggunakan salt dari waktu sekarang. + +```go +package main + +import "crypto/sha1" +import "fmt" +import "time" + +func doHashUsingSalt(text string) (string, string) { + var salt = fmt.Sprintf("%d", time.Now().UnixNano()) + var saltedText = fmt.Sprintf("text: '%s', salt: %s", text, salt) + fmt.Println(saltedText) + var sha = sha1.New() + sha.Write([]byte(saltedText)) + var encrypted = sha.Sum(nil) + + return fmt.Sprintf("%x", encrypted), salt +} +``` + +Salt yang digunakan adalah hasil dari ekspresi `time.Now().UnixNano()`. Hasilnya akan selalu unik setiap detiknya, karena scope terendah waktu pada fungsi tersebut adalah *nano second* atau nano detik. + +Selanjutnya test fungsi yang telah dibuat beberapa kali. + +```go +func main() { + var text = "this is secret" + fmt.Printf("original : %s\n\n", text) + + var hashed1, salt1 = doHashUsingSalt(text) + fmt.Printf("hashed 1 : %s\n\n", hashed1) + // 929fd8b1e58afca1ebbe30beac3b84e63882ee1a + + var hashed2, salt2 = doHashUsingSalt(text) + fmt.Printf("hashed 2 : %s\n\n", hashed2) + // cda603d95286f0aece4b3e1749abe7128a4eed78 + + var hashed3, salt3 = doHashUsingSalt(text) + fmt.Printf("hashed 3 : %s\n\n", hashed3) + // 9e2b514bca911cb76f7630da50a99d4f4bb200b4 + + _, _, _ = salt1, salt2, salt3 +} +``` + +Hasil ekripsi fungsi `doHashUsingSalt` akan selalu beda, karena salt yang digunakan adalah waktu. + +![Hashing dengan salt](images/A.44_2_hash_salt_sha1.png) + +Metode ini sering dipakai untuk enkripsi password user. Salt dan data hasil hash harus disimpan pada database, karena digunakan dalam pencocokan password setiap user melakukan login. + +--- + + diff --git a/45-command-line-args-flag.md b/45-command-line-args-flag.md new file mode 100644 index 000000000..d225a1400 --- /dev/null +++ b/45-command-line-args-flag.md @@ -0,0 +1,137 @@ +# A.45. Arguments & Flag + +**Arguments** adalah data opsional yang disisipkan ketika eksekusi program. Sedangkan **flag** merupakan ekstensi dari argument. Dengan flag, penulisan argument menjadi lebih rapi dan terstruktur. + +Di bab ini kita akan belajar tentang penggunaan arguments dan flag. + +## A.45.1. Penggunaan Arguments + +Data arguments bisa didapat lewat variabel `os.Args` (package `os` perlu di-import terlebih dahulu). Data tersebut tersimpan dalam bentuk array dengan pemisah adalah tanda spasi. + +Berikut merupakan contoh penggunaannya. + +```go +package main + +import "fmt" +import "os" + +func main() { + var argsRaw = os.Args + fmt.Printf("-> %#v\n", argsRaw) + // []string{".../bab45", "banana", "potato", "ice cream"} + + var args = argsRaw[1:] + fmt.Printf("-> %#v\n", args) + // []string{"banana", "potato"} +} +``` + +Pada saat eksekusi program disisipkan juga argument-nya. Sebagai contoh disisipkan 3 buah data sebagai argumen, yaitu: `banana`, `potato`, dan `ice cream`. + +Untuk eksekusinya sendiri bisa menggunakan `go run` ataupun dengan cara build-execute. + + - Menggunakan `go run` + + ``` + $ go run bab45.go banana potato "ice cream" + ``` + + - Menggunakan `go build` + + ``` + $ go build bab45.go + $ ./bab45 banana potato "ice cream" + ``` + +Variabel `os.Args` mengembalikan tak hanya arguments saja, tapi juga path file executable (jika eksekusi-nya menggunakan `go run` maka path akan merujuk ke folder temporary). Gunakan `os.Args[1:]` untuk mengambil slice arguments-nya saja. + +![Pemanfaatan arguments](images/A.45_1_argument.png) + +Bisa dilihat pada kode di atas, bahwa untuk data argumen yang ada karakter spasi nya ( ), maka harus diapit tanda petik (`"`), agar tidak dideteksi sebagai 2 argumen. + +## A.45.2. Penggunaan Flag + +Flag memiliki kegunaan yang sama seperti arguments, yaitu untuk *parameterize* eksekusi program, dengan penulisan dalam bentuk key-value. Berikut merupakan contoh penerapannya. + +```go +package main + +import "flag" +import "fmt" + +func main() { + var name = flag.String("name", "anonymous", "type your name") + var age = flag.Int64("age", 25, "type your age") + + flag.Parse() + fmt.Printf("name\t: %s\n", *name) + fmt.Printf("age\t: %d\n", *age) +} +``` + +Cara penulisan arguments menggunakan flag: + +``` +$ go run bab45.go -name="john wick" -age=28 +``` + +Tiap argument harus ditentukan key, tipe data, dan nilai default-nya. Contohnya seperti pada `flag.String()` di atas. Agar lebih mudah dipahami, mari kita bahas kode berikut. + +```go +var dataName = flag.String("name", "anonymous", "type your name") +fmt.Println(*dataName) +``` + +Kode tersebut maksudnya adalah, disiapkan flag bertipe `string`, dengan key adalah `name`, dengan nilai default `"anonymous"`, dan keterangan `"type your name"`. Nilai flag nya sendiri akan disimpan kedalam variabel `dataName`. + +Nilai balik fungsi `flag.String()` adalah string pointer, jadi perlu di-*dereference* terlebih dahulu agar bisa mendapatkan nilai aslinya (`*dataName`). + +![Contoh penggunaan flag](images/A.45_2_flag.png) + +Flag yang nilainya tidak di set, secara otomatis akan mengembalikan nilai default. + +Tabel berikut merupakan macam-macam fungsi flag yang tersedia untuk tiap jenis tipe data. + +| Nama Fungsi | Return Value | +| :---------- | :-------------- | +| `flag.Bool(name, defaultValue, usage)` | `*bool` | +| `flag.Duration(name, defaultValue, usage)` | `*time.Duration` | +| `flag.Float64(name, defaultValue, usage)` | `*float64` | +| `flag.Int(name, defaultValue, usage)` | `*int` | +| `flag.Int64(name, defaultValue, usage)` | `*int64` | +| `flag.String(name, defaultValue, usage)` | `*string` | +| `flag.Uint(name, defaultValue, usage)` | `*uint` | +| `flag.Uint64(name, defaultValue, usage)` | `*uint64` | + +## A.45.3. Deklarasi Flag Dengan Cara Passing Reference Variabel Penampung Data + +Sebenarnya ada 2 cara deklarasi flag yang bisa digunakan, dan cara di atas merupakan cara pertama. + +Cara kedua mirip dengan cara pertama, perbedannya adalah kalau di cara pertama nilai pointer flag dikembalikan lalu ditampung variabel; sedangkan pada cara kedua, nilainya diambil lewat parameter pointer. + +Agar lebih jelas perhatikan contoh berikut. + +```go +// cara ke-1 +var data1 = flag.String("name", "anonymous", "type your name") +fmt.Println(*data1) + +// cara ke-2 +var data2 string +flag.StringVar(&data2, "gender", "male", "type your gender") +fmt.Println(data2) +``` + +Tinggal tambahkan suffix `Var` pada pemanggilan nama fungsi flag yang digunakan (contoh `flag.IntVar()`, `flag.BoolVar()`, dll), lalu disisipkan referensi variabel penampung flag sebagai parameter pertama. + +Kegunaan dari parameter terakhir method-method flag adalah untuk memunculkan hints atau petunjuk arguments apa saja yang bisa dipakai, ketika argument `--help` ditambahkan saat eksekusi program. + +![Contoh penggunaan flag](images/A.45_3_flag_info.png) + +--- + + diff --git a/46-exec.md b/46-exec.md new file mode 100644 index 000000000..235e2703f --- /dev/null +++ b/46-exec.md @@ -0,0 +1,58 @@ +# A.46. Exec + +**Exec** digunakan untuk eksekusi perintah command line lewat kode program. Command yang bisa dieksekusi adalah semua command yang bisa dieksekusi di terminal (atau CMD untuk pengguna Windows). + +## A.46.1. Penggunaan Exec + +Go menyediakan package `exec` berisikan banyak fungsi untuk keperluan eksekusi perintah CLI. + +Cara untuk eksekusi command cukup mudah, yaitu dengan menuliskan command dalam bentuk string, diikuti arguments-nya (jika ada) sebagai parameter variadic pada fungsi `exec.Command()`. + +```go +package main + +import "fmt" +import "os/exec" + +func main() { + var output1, _ = exec.Command("ls").Output() + fmt.Printf(" -> ls\n%s\n", string(output1)) + + var output2, _ = exec.Command("pwd").Output() + fmt.Printf(" -> pwd\n%s\n", string(output2)) + + var output3, _ = exec.Command("git", "config", "user.name").Output() + fmt.Printf(" -> git config user.name\n%s\n", string(output3)) +} +``` + +Fungsi `exec.Command()` digunakan untuk menjalankan command. Fungsi tersebut bisa langsung di-chain dengan method `Output()`, jika ingin mendapatkan outputnya. Output yang dihasilkan berbentuk `[]byte`, gunakan cast ke string untuk mengambil bentuk string-nya. + +![Ekeskusi command menggunakan exec](images/A.46_1_exec.png) + +## A.46.2. Rekomendasi Penggunaan Exec + +Kadang kala, pada saat eksekusi command yang sudah jelas-jelas ada (seperti `ls`, `dir`, atau lainnya) kita menemui error yang mengatakan command not found. Hal itu terjadi karena executable dari command-command tersebut tidak ada. Seperti di windows tidak ada `dir.exe` dan lainnya. Di OS non-windows-pun juga demikian. + +Untuk mengatasi masalah ini, tambahkan `bash -c` pada linux/nix command atau `cmd /C` untuk windows. + +```go +if runtime.GOOS == "windows" { + output, err = exec.Command("cmd", "/C", "git config user.name").Output() +} else { + output, err = exec.Command("bash", "-c", "git config user.name") +} +``` + +Statement `runtime.GOOS` mengembalikan informasi sistim operasi dalam string. + +## A.46.3. Method Exec Lainnya + +Selain `.Output()` ada sangat banyak sekali API untuk keperluan komunikasi dengan OS/CLI yang bisa dipergunakan. Detailnya silakan langsung merujuk ke dokumentasi [https://golang.org/pkg/os/exec/](https://golang.org/pkg/os/exec/) + +--- + + diff --git a/47-file.md b/47-file.md new file mode 100644 index 000000000..c359fc115 --- /dev/null +++ b/47-file.md @@ -0,0 +1,159 @@ +# A.47. File + +Ada beberapa cara yang bisa digunakan untuk operasi file di Go. Pada bab ini kita akan mempelajari teknik yang paling dasar, yaitu dengan memanfaatkan `os.File`. + +## A.47.1. Membuat File Baru + +Pembuatan file di Go sangatlah mudah, cukup dengan memanggil fungsi `os.Create()` lalu memasukkan path file yang ingin dibuat sebagai parameter. Jika ternyata file yang akan dibuat sudah ada, maka akan ditimpa. Bisa memanfaatkan `os.IsNotExist()` untuk mendeteksi apakah file sudah dibuat atau belum. + +Berikut merupakan contoh pembuatan file. + +```go +package main + +import "fmt" +import "os" + +var path = "/Users/novalagung/Documents/temp/test.txt" + +func isError(err error) bool { + if err != nil { + fmt.Println(err.Error()) + } + + return (err != nil) +} + +func createFile() { + // deteksi apakah file sudah ada + var _, err = os.Stat(path) + + // buat file baru jika belum ada + if os.IsNotExist(err) { + var file, err = os.Create(path) + if isError(err) { return } + defer file.Close() + } + + fmt.Println("==> file berhasil dibuat", path) +} + +func main() { + createFile() +} +``` + +Fungsi `os.Stat()` mengembalikan 2 data, yaitu informasi tetang path yang dicari, dan error (jika ada). Masukkan error kembalian fungsi tersebut sebagai parameter fungsi `os.IsNotExist()`, untuk mendeteksi apakah file yang akan dibuat sudah ada. Jika belum ada, maka fungsi tersebut akan mengembalikan nilai `true`. + +Fungsi `os.Create()` digunakan untuk membuat file pada path tertentu. Fungsi ini mengembalikan objek `*os.File` dari file yang bersangkutan. File yang baru terbuat statusnya adalah otomatis **open**, maka dari itu perlu untuk di-**close** menggunakan method `file.Close()` setelah file tidak digunakan lagi. + +Membiarkan file terbuka ketika sudah tak lagi digunakan bukan hal yang baik, karena efeknya ke memory dan akses ke file itu sendiri, file akan di-lock sehingga tidak bisa digunakan oleh proses lain selama belum status file masih open atau belum di-close. + +![Membuat file baru](images/A.47_1_create.png) + +## A.47.2. Mengedit Isi File + +Untuk mengedit file, yang perlu dilakukan pertama adalah membuka file dengan level akses **write**. Setelah mendapatkan objek file-nya, gunakan method `WriteString()` untuk pengisian data. Terakhir panggil method `Sync()` untuk menyimpan perubahan. + +```go +func writeFile() { + // buka file dengan level akses READ & WRITE + var file, err = os.OpenFile(path, os.O_RDWR, 0644) + if isError(err) { return } + defer file.Close() + + // tulis data ke file + _, err = file.WriteString("halo\n") + if isError(err) { return } + _, err = file.WriteString("mari belajar golang\n") + if isError(err) { return } + + // simpan perubahan + err = file.Sync() + if isError(err) { return } + + fmt.Println("==> file berhasil di isi") +} + +func main() { + writeFile() +} +``` + +Pada program di atas, file dibuka dengan level akses **read** dan **write** dengan kode permission **0664**. Setelah itu, beberapa string diisikan kedalam file tersebut menggunakan `WriteString()`. Di akhir, semua perubahan terhadap file akan disimpan dengan dipanggilnya `Sync()`. + +![Mengedit file](images/A.47_2_write.png) + +## A.47.3. Membaca Isi File + +File yang ingin dibaca harus dibuka terlebih dahulu menggunakan fungsi `os.OpenFile()` dengan level akses minimal adalah **read**. Setelah itu, gunakan method `Read()` dengan parameter adalah variabel, yang dimana hasil proses baca akan disimpan ke variabel tersebut. + +```go +// tambahkan di bagian import package io +import "io" + +func readFile() { + // buka file + var file, err = os.OpenFile(path, os.O_RDWR, 0644) + if isError(err) { return } + defer file.Close() + + // baca file + var text = make([]byte, 1024) + for { + n, err := file.Read(text) + if err != io.EOF { + if isError(err) { break } + } + if n == 0 { + break + } + } + if isError(err) { return } + + fmt.Println("==> file berhasil dibaca") + fmt.Println(string(text)) +} + +func main() { + readFile() +} +``` + +Pada kode di atas `os.OpenFile()` digunakan untuk membuka file. Fungsi tersebut memiliki beberapa parameter. + + 1. Parameter pertama adalah path file yang akan dibuka. + 2. Parameter kedua adalah level akses. `os.O_RDONLY` maksudnya adalah **read only**. + 3. Parameter ketiga adalah permission file-nya. + +Variabel `text` disiapkan bertipe slice `[]byte` dengan alokasi elemen 1024. Variabel tersebut bertugas menampung data hasil statement `file.Read()`. Proses pembacaan file akan dilakukan terus menerus, berurutan dari baris pertama hingga akhir. + +Error yang muncul ketika eksekusi `file.Read()` akan di-filter, ketika error tersebut adalah selain `io.EOF` maka proses baca file akan berlanjut. Error `io.EOF` sendiri menandakan bahwa file yang sedang dibaca adalah baris terakhir isi atau **end of file**. + +![Membaca isi file](images/A.47_3_read.png) + +## A.47.4. Menghapus File + +Cara menghapus file sangatlah mudah, cukup panggil fungsi `os.Remove()`, masukan path file yang ingin dihapus sebagai parameter. + +```go +func deleteFile() { + var err = os.Remove(path) + if isError(err) { return } + + fmt.Println("==> file berhasil di delete") +} + +func main() { + deleteFile() +} +``` + +![Menghapus file](images/A.47_4_delete.png) + +--- + + diff --git a/48-web.md b/48-web.md new file mode 100644 index 000000000..ae4222bda --- /dev/null +++ b/48-web.md @@ -0,0 +1,130 @@ +# A.48. Web + +Go menyediakan package `net/http`, berisi berbagai macam fitur untuk keperluan pembuatan aplikasi berbasi web. Termasuk didalamnya web server, routing, templating, dan lainnya. + +Go memiliki web server sendiri, dan web server tersebut berada di dalam Go, tdak seperti bahasa lain yang server nya terpisah dan perlu di-instal sendiri (seperti PHP yang memerlukan Apache, .NET yang memerlukan IIS). + +Di bab ini kita akan belajar cara pembuatan aplikasi web sederhanda dan pemanfaatan template untuk mendesain view. + +## A.48.1. Membuat Aplikasi Web Sederhanda + +Package `net/http` memiliki banyak sekali fungsi yang bisa dimanfaatkan. Di bagian ini kita akan mempelajari beberapa fungsi penting seperti *routing* dan *start server*. + +Program dibawah ini merupakan contoh sederhana untuk memunculkan text di web ketika url tertentu diakses. + +```go +package main + +import "fmt" +import "net/http" + +func index(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "apa kabar!") +} + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "halo!") + }) + + http.HandleFunc("/index", index) + + fmt.Println("starting web server at http://localhost:8080/") + http.ListenAndServe(":8080", nil) +} +``` + +Jalankan program tersebut. + +![Eksekusi program](images/A.48_0_start_server.png) + +Jika muncul dialog **Do you want the application “bab48” to accept incoming network connections?** atau sejenis, pilih allow. Setelah itu, buka url [http://localhost/](http://localhost/) dan [http://localhost/index](http://localhost/index/) lewat browser. + +![Contoh penerapan net/http](images/A.48_1_web.png) + +Fungsi `http.HandleFunc()` digunakan untuk routing aplikasi web. Maksud dari routing adalah penentuan aksi ketika url tertentu diakses oleh user. + +Pada kode di atas 2 rute didaftarkan, yaitu `/` dan `/index`. Aksi dari rute `/` adalah menampilkan text `"halo"` di halaman website. Sedangkan `/index` menampilkan text `"apa kabar!"`. + +Fungsi `http.HandleFunc()` memiliki 2 buah parameter yang harus diisi. Parameter pertama adalah rute yang diinginkan. Parameter kedua adalah *callback* atau aksi ketika rute tersebut diakses. Callback tersebut bertipe fungsi `func(w http.ResponseWriter, r *http.Request)`. + +Pada pendaftaran rute `/index`, callback-nya adalah fungsi `index()`, hal seperti ini diperbolehkan asalkan tipe dari fungsi tersebut sesuai. + +Fungsi `http.listenAndServe()` digunakan untuk menghidupkan server sekaligus menjalankan aplikasi menggunakan server tersebut. Di Go, 1 web aplikasi adalah 1 buah server berbeda. + +Pada contoh di atas, server dijalankan pada port `8080`. + +Perlu diingat, setiap ada perubahan pada file `.go`, `go run` harus dipanggil lagi. + +Untuk menghentikan web server, tekan **CTRL+C** pada terminal atau CMD, dimana pengeksekusian aplikasi berlangsung. + +## A.48.2. Penggunaan Template Web + +Template engine memberikan kemudahan dalam mendesain tampilan view aplikasi website. Dan kabar baiknya Go menyediakan engine template sendiri, dengan banyak fitur yang tersedia didalamnya. + +Di sini kita akan belajar contoh sederhana penggunaan template untuk menampilkan data. Pertama siapkan dahulu template nya. Buat file `template.html` lalu isi dengan kode berikut. + +```html + + + Go learn net/http + + +

Hello {{\.Name}} !

+

{{\.Message}}

+ + +``` + +Kode `{{.Name}}` artinya memunculkan isi data property `Name` yang dikirim dari router. Kode tersebut nantinya di-replace dengan isi variabel `Name`. + +Selanjutnya ubah isi file `.go` dengan kode berikut. + +```go +package main + +import "fmt" +import "html/template" +import "net/http" + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + var data = map[string]string{ + "Name": "john wick", + "Message": "have a nice day", + } + + var t, err = template.ParseFiles("template.html") + if err != nil { + fmt.Println(err.Error()) + return + } + + t.Execute(w, data) + }) + + fmt.Println("starting web server at http://localhost:8080/") + http.ListenAndServe(":8080", nil) +} +``` + +Jalankan, lalu buka [http://localhost:8080/](http://localhost:8080/), maka data `Nama` dan `Message` akan muncul di view. + +![Penggunaan template](images/A.48_2_template.png) + +Fungsi `template.ParseFiles()` digunakan untuk parsing template, mengembalikan 2 data yaitu instance template-nya dan error (jika ada). Pemanggilan method `Execute()` akan membuat hasil parsing template ditampilkan ke layar web browser. + +Pada kode di atas, variabel `data` disisipkan sebagai parameter ke-2 method `Execute()`. Isi dari variabel tersebut bisa diakses di-view dengan menggunakan notasi `{{.NAMA_PROPERTY}}` (nama variabel sendiri tidak perlu dituliskan, langsung nama property didalamnya). + +Pada contoh di atas, statement di view `{{.Name}}` akan menampilkan isi dari `data.Name`. + +## A.48.3. Advance Web Programming + +Sampai bab ini yang kita pelajari adalah dasar-dasar pemrograman Go, di bahas per topik adalah per bab. Nantinya jika sudah masuk [Bab B.1. Golang Web App: Hello World](/B-1-golang-web-hello-world.html) hingga seterusnya akan membahas mostly mengenai pemrograman web, jadi sabar dulu. Mari kita selesaikan dulu yang fundamental sebelum masuk ke web development. + +--- + + diff --git a/49-url-parsing.md b/49-url-parsing.md new file mode 100644 index 000000000..3a6d730d0 --- /dev/null +++ b/49-url-parsing.md @@ -0,0 +1,44 @@ +# A.49. URL Parsing + +Data string url bisa dikonversi kedalam bentuk `url.URL`. Dengan menggunakan tipe tersebut akan ada banyak informasi yang bisa kita manfaatkan, diantaranya adalah jenis protokol yang digunakan, path yang diakses, query, dan lainnya. + +Berikut adalah contoh sederhana konversi string ke `url.URL`. + +```go +package main + +import "fmt" +import "net/url" + +func main() { + var urlString = "http://kalipare.com:80/hello?name=john wick&age=27" + var u, e = url.Parse(urlString) + if e != nil { + fmt.Println(e.Error()) + return + } + + fmt.Printf("url: %s\n", urlString) + + fmt.Printf("protocol: %s\n", u.Scheme) // http + fmt.Printf("host: %s\n", u.Host) // kalipare.com:80 + fmt.Printf("path: %s\n", u.Path) // /hello + + var name = u.Query()["name"][0] // john wick + var age = u.Query()["age"][0] // 27 + fmt.Printf("name: %s, age: %s\n", name, age) +} +``` + +Fungsi `url.Parse()` digunakan untuk parsing string ke bentuk url. Mengembalikan 2 data, variabel objek bertipe `url.URL` dan error (jika ada). Lewat variabel objek tersebut pengaksesan informasi url akan menjadi lebih mudah, contohnya seperti nama host bisa didapatkan lewat `u.Host`, protokol lewat `u.Scheme`, dan lainnya. + +Selain itu, query yang ada pada url akan otomatis diparsing juga, menjadi bentuk `map[string][]string`, dengan key adalah nama elemen query, dan value array string yang berisikan value elemen query. + +![Pengaksesan elemen URL](images/A.49_1_parse_url.png) + +--- + + diff --git a/5-go-command.md b/5-go-command.md new file mode 100644 index 000000000..892275de1 --- /dev/null +++ b/5-go-command.md @@ -0,0 +1,97 @@ +# A.5. Command + +Pengembangan aplikasi Go tak jauh dari hal-hal yang berbau CLI atau **Command Line Interface**. Proses kompilasi, testing, eksekusi program, semua dilakukan lewat command line. + +Go menyediakan command `go`, di bab ini kita akan belajar mengenai pemanfaatannya. + +## A.5.1. Command `go run` + +Command `go run` digunakan untuk eksekusi file program (file ber-ekstensi `.go`). Cara penggunaannya dengan menuliskan command tersebut diikut argumen nama file. + +Berikut adalah contoh penerapan `go run` untuk eksekusi file program `bab5.go` yang tersimpan di path `$GOPATH/src/belajar-golang`. + +```bash +$ cd $GOPATH/src/belajar-golang +$ go run bab5.go +``` + +![Eksekusi file program menggunakan `go run`](images/A.5_1_go_run.png) + +Command `go run` hanya bisa digunakan pada file yang package-nya adalah **main**. Lebih jelasnya dibahas pada bab selanjutnya (bab 6). + +Jika ada banyak file yang ber-package `main`, dan file-file tersebut di-import di file utama, maka eksekusinya adalah dengan menyisipkan semua file sebagai argument `go run` (lebih jelasnya akan dibahas pada bab 25). Contohnya bisa dilihat pada kode berikut. + +```bash +$ go run bab5.go library.go +``` + +![Eksekusi banyak file main](images/A.5_2_go_run_multi.png) + +Atau bisa dengan menggunakan `*.go`, tanpa tidak perlu menuliskan nama-nama file program yang ada. + +```bash +$ go run *.go +``` + +## A.5.2. Command `go test` + +Go menyediakan package `testing`, berguna untuk keperluan unit testing. File yang akan di-test harus ber-suffix `_test.go`. + +Berikut adalah contoh penggunaan command `go test` untuk testing file `bab5_test.go`. + +```bash +$ go test bab5_test.go +``` + +![Unit testing menggunakan `go test`](images/A.5_3_go_test.png) + +## A.5.3. Command `go build` + +Command ini digunakan untuk mengkompilasi file program. + +Sebenarnya ketika eksekusi program menggunakan `go run`, terjadi proses kompilasi juga, file hasil kompilasi akan disimpan pada folder temporary untuk selanjutnya langsung dieksekusi. + +Berbeda dengan `go build`, command ini menghasilkan file executable pada folder yang sedang aktif. Contohnya bisa dilihat pada kode berikut. + +![Kompilasi file program menghasilkan file executable](images/A.5_4_go_build.png) + +Pada contoh di atas, file `bab5.go` di-build, menghasilkan file baru pada folder yang sama, yaitu `bab5`, yang kemudian dieksekusi. + +> Pada pengguna Windows, file executable ber-ekstensi `.exe`. + +## A.5.4. Command `go install` + +Command `go install` memiliki fungsi yang sama dengan `go build`, hanya saja setelah proses kompilasi selesai, dilanjutkan ke proses instalasi program yang bersangkutan. + +Target eksekusi harus berupa folder proyek (bukan file `.go`), dan path folder tersebut dituliskan relatif terhadap `$GOPATH/src`. Contoh: + +```bash +$ go install github.com/novalagung/godong +``` + +`go install` menghasilkan output berbeda untuk package `main` dan non-main. + + - Pada package **non-main**, menghasilkan file berekstensi `.a` tersimpan dalam folder `$GOPATH/pkg`. + - Pada package **main**, menghasilkan file *executable* tersimpan dalam folder `$GOPATH/bin`. + +Berikut merupakan contoh penerapan `go install`. + +![Contoh penggunaan `go install`](images/A.5_7_go_install.png) + +Pada kode di atas bisa dilihat command `go install` dieksekusi 2 kali. + + 1. Pada package non-main, `github.com/novalagung/godong`. Hasil instalasi adalah file berekstensi `.a` tersimpan pada folder `$GOPATH/pkg`. + 2. Pada package main, `github.com/novalagung/godong/godong_test`. Hasil instalasi adalah file executable tersimpan pada folder `$GOPATH/bin`. + +## A.5.5. Command `go get` + +Command ini berbeda dengan command-command yang sudah dibahas di atas. `go get` digunakan untuk men-download package. Sebagai contoh saya ingin men-download package **Mgo**. + +```bash +$ go get gopkg.in/mgo.v2 +$ ls $GOPATH/src/gopkg.in/mgo.v2 +``` + +![Download package menggunakan `go get`](images/A.5_6_go_get.png) + +[gopkg.in/mgo.v2](https://gopkg.in/mgo.v2) adalah URL package mgo. Package yang sudah ter-download tersimpan dalam `$GOPATH/src`, dengan struktur folder sesuai dengan URL package-nya. Sebagai contoh, package MGO di atas tersimpan di `$GOPATH/src/gopkg.in/mgo.v2`. diff --git a/50-json.md b/50-json.md new file mode 100644 index 000000000..3fad46b04 --- /dev/null +++ b/50-json.md @@ -0,0 +1,137 @@ +# A.50. JSON + +**JSON** atau *Javascript Object Notation* adalah notasi standar yang umum digunakan untuk komunikasi data dalam web. JSON merupakan subset dari *javascript*. + +Go menyediakan package `encoding/json` yang berisikan banyak fungsi untuk kebutuhan operasi json. + +Di bab ini, kita akan belajar cara untuk konverstri string yang berbentuk json menjadi objek Go, dan sebaliknya. + +## A.50.1. Decode JSON Ke Variabel Objek Struct + +Di Go, data json dituliskan sebagai `string`. Dengan menggunakan `json.Unmarshal`, json string bisa dikonversi menjadi bentuk objek, entah itu dalam bentuk `map[string]interface{}` ataupun objek struct. + +Program berikut ini adalah contoh cara decoding json ke bentuk objek. Pertama import package yang dibutuhkan, lalu siapkan struct `User`. + +```go +package main + +import "encoding/json" +import "fmt" + +type User struct { + FullName string `json:"Name"` + Age int +} +``` + +Struct `User` ini nantinya digunakan untuk membuat variabel baru penampung hasil decode json string. Proses decode sendiri dilakukan lewat fungsi `json.Unmarshal()`, dengan json string tersebut dimasukan ke statement fungsi tersebut. + +Silakan tulis kode berikut. + +```go +func main() { + var jsonString = `{"Name": "john wick", "Age": 27}` + var jsonData = []byte(jsonString) + + var data User + + var err = json.Unmarshal(jsonData, &data) + if err != nil { + fmt.Println(err.Error()) + return + } + + fmt.Println("user :", data.FullName) + fmt.Println("age :", data.Age) +} +``` + +Fungsi unmarshal hanya menerima data json dalam bentuk `[]byte`, maka dari itu data json string pada kode di atas di-casting terlebih dahulu ke tipe `[]byte` sebelum dipergunakan pada fungsi unmarshal. + +Juga, perlu diperhatikan, argument ke-2 fungsi unmarshal harus diisi dengan **pointer** dari objek yang nantinya akan menampung hasilnya. + +![Decode data json ke variabel objek](images/A.50_1_decode.png) + +Jika kita perhatikan lagi, pada struct `User`, salah satu property-nya yaitu `FullName` memiliki **tag** `json:"Name"`. Tag tersebut digunakan untuk mapping informasi json ke property yang bersangkutan. + +Data json yang akan diparsing memiliki 2 property yaitu `Name` dan `Age`. Kebetulan penulisan `Age` pada data json dan pada struktur struct adalah sama, berbeda dengan `Name` yang tidak ada pada struct. + +Dengan menambahkan tag json, maka property `FullName` struct akan secara cerdas menampung data json property `Name`. + +> Pada kasus decoding data json string ke variabel objek struct, semua level akses property struct penampung harus publik. + +## A.50.2. Decode JSON Ke `map[string]interface{}` & `interface{}` + +Tak hanya ke objek cetakan struct, target decoding data json juga bisa berupa variabel bertipe `map[string]interface{}`. + +```go +var data1 map[string]interface{} +json.Unmarshal(jsonData, &data1) + +fmt.Println("user :", data1["Name"]) +fmt.Println("age :", data1["Age"]) +``` + +Variabel bertipe `interface{}` juga bisa digunakan untuk menampung hasil decode. Dengan catatan pada pengaksesan nilai property, harus dilakukan casting terlebih dahulu ke `map[string]interface{}`. + +```go +var data2 interface{} +json.Unmarshal(jsonData, &data2) + +var decodedData = data2.(map[string]interface{}) +fmt.Println("user :", decodedData["Name"]) +fmt.Println("age :", decodedData["Age"]) +``` + +## A.50.3. Decode Array JSON Ke Array Objek + +Decode data dari array json ke slice/array objek masih sama, siapkan saja variabel penampung hasil decode dengan tipe slice struct. Contohnya bisa dilihat pada kode berikut. + +```go +var jsonString = `[ + {"Name": "john wick", "Age": 27}, + {"Name": "ethan hunt", "Age": 32} +]` + +var data []User + +var err = json.Unmarshal([]byte(jsonString), &data) +if err != nil { + fmt.Println(err.Error()) + return +} + +fmt.Println("user 1:", data[0].FullName) +fmt.Println("user 2:", data[1].FullName) +``` + +## A.50.4. Encode Objek Ke JSON String + +Setelah sebelumnya dijelaskan beberapa cara decode data dari json string ke objek, sekarang kita akan belajar cara **encode** data objek ke bentuk json string. + +Fungsi `json.Marshal` digunakan untuk decoding data ke json string. Sumber data bisa berupa variabel objek cetakan struct, `map[string]interface{}`, atau slice. + +Pada contoh berikut, data slice struct dikonversi ke dalam bentuk json string. Hasil konversi berupa `[]byte`, casting terlebih dahulu ke tipe `string` agar bisa ditampilkan bentuk json string-nya. + +```go +var object = []User{{"john wick", 27}, {"ethan hunt", 32}} +var jsonData, err = json.Marshal(object) +if err != nil { + fmt.Println(err.Error()) + return +} + +var jsonString = string(jsonData) +fmt.Println(jsonString) +``` + +Output: + +![Encode data ke JSON](images/A.50_2_encode.png) + +--- + + diff --git a/51-web-json-api.md b/51-web-json-api.md new file mode 100644 index 000000000..04da6fd82 --- /dev/null +++ b/51-web-json-api.md @@ -0,0 +1,135 @@ +# A.51. Web Service API (JSON Data) + +Pada bab ini kita akan mengkombinasikan pembahasan 2 bab sebelumnya, yaitu web programming dan JSON, untuk membuat sebuah web service API dengan tipe data reponse berbentuk JSON. + +> Web Service API adalah sebuah web yang menerima request dari client dan menghasilkan response, biasa berupa JSON/XML. + +## A.51.1. Pembuatan Web API + +Pertama siapkan terlebih dahulu struct dan beberapa data sample. + +```go +package main + +import "encoding/json" +import "net/http" +import "fmt" + +type student struct { + ID string + Name string + Grade int +} + +var data = []student{ + student{"E001", "ethan", 21}, + student{"W001", "wick", 22}, + student{"B001", "bourne", 23}, + student{"B002", "bond", 23}, +} +``` + +Struct `student` di atas digunakan sebagai tipe elemen slice sample data, ditampung variabel `data`. + +Selanjutnya buat fungsi `users()` untuk handle endpoint `/users`. Didalam fungsi tersebut ada proses deteksi jenis request lewat property `r.Method()`, untuk mencari tahu apakah jenis request adalah **POST** atau **GET** atau lainnya. + +```go +func users(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method == "POST" { + var result, err = json.Marshal(data) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Write(result) + return + } + + http.Error(w, "", http.StatusBadRequest) +} +``` + +Jika request adalah POST, maka data yang di-encode ke JSON dijadikan sebagai response. + +Statement `w.Header().Set("Content-Type", "application/json")` digunakan untuk menentukan tipe response, yaitu sebagai JSON. Sedangkan `r.Write()` digunakan untuk mendaftarkan data sebagai response. + +Selebihnya, jika request tidak valid, response di set sebagai error menggunakan fungsi `http.Error()`. + +Siapkan juga handler untuk endpoint `/user`. Perbedaan endpoint ini dengan `/users` di atas adalah: + + - Endpoint `/users` menghasilkan semua sample data yang ada (array). + - Endpoint `/user` menghasilkan satu buah data saja, diambel dari data sample berdasarkan `ID`-nya. Pada endpoint ini, client harus mengirimkan juga informasi `ID` data yang dicari. + +```go +func user(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method == "POST" { + var id = r.FormValue("id") + var result []byte + var err error + + for _, each := range data { + if each.ID == id { + result, err = json.Marshal(each) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Write(result) + return + } + } + + http.Error(w, "User not found", http.StatusBadRequest) + return + } + + http.Error(w, "", http.StatusBadRequest) +} +``` + +Method `r.FormValue()` digunakan untuk mengambil data form yang dikirim dari client, pada konteks ini data yang dimaksud adalah `ID`. + +Dengan menggunakan `ID` tersebut dicarilah data yang relevan. Jika ada, maka dikembalikan sebagai response. Jika tidak ada maka error **400, Bad Request** dikembalikan dengan pesan **User Not Found**. + +Terakhir, implementasikan kedua handler di atas. + +```go +func main() { + http.HandleFunc("/users", users) + http.HandleFunc("/user", user) + + fmt.Println("starting web server at http://localhost:8080/") + http.ListenAndServe(":8080", nil) +} +``` + +Jalankan program, sekarang web server sudah live dan bisa dikonsumsi datanya. + +![Web API Server dijalankan](images/A.51_1_server.png) + +## A.51.2. Test Web Service API + +Setelah web server sudah berjalan, web service yang telah dibuat perlu untuk di-tes. Di sini saya menggunakan Google Chrome plugin bernama [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en) untuk mengetes API yang sudah dibuat. + + - Test endpoint `/users`, apakah data yang dikembalikan sudah benar. + + ![Test `/users`](images/A.51_2_test_api_users.png) + + - Test endpoint `/user`, isi form data `id` dengan nilai `E001`. + + ![Test `/user`](images/A.51_3_test_api_user.png) + +--- + + diff --git a/52-http-request.md b/52-http-request.md new file mode 100644 index 000000000..04d6228b6 --- /dev/null +++ b/52-http-request.md @@ -0,0 +1,178 @@ +# A.52. HTTP Request + +Di bab sebelumnya telah dibahas bagaimana membuat Web Service API yang mem-provide data JSON, pada bab ini kita akan belajar mengenai cara untuk mengkonsumsi data tersebut. + +Pastikan anda sudah mempraktekkan apa-apa yang ada pada bab sebelumnya [Bab A.51. Web Service API (JSON Data)](/51-web-json-api.html), karena web service yang telah dibuat pada bab tersebut dipergunakan juga pada bab ini. + +![Jalankan web server](images/A.51_1_server.png) + +## A.52.1. Penggunaan HTTP Request + +Package `net/http`, selain berisikan tools untuk keperluan pembuatan web, juga berisikan fungsi-fungsi untuk melakukan http request. Salah satunya adalah `http.NewRequest()` yang akan kita bahas di sini. + +Sebelumnya, import package yang dibutuhkan. Dan siapkan struct `student` yang nantinya akan dipakai sebagai tipe data reponse dari web API. Struk tersebut skema nya sama dengan yang ada pada bab 51. + +```go +package main + +import "fmt" +import "net/http" +import "encoding/json" + +var baseURL = "http://localhost:8080" + +type student struct { + ID string + Name string + Grade int +} +``` + +Setelah itu buat fungsi `fetchUsers()`. Fungsi ini bertugas melakukan request ke [http://localhost:8080/users](http://localhost:8080/users), menerima response dari request tersebut, lalu menampilkannya. + +```go +func fetchUsers() ([]student, error) { + var err error + var client = &http.Client{} + var data []student + + request, err := http.NewRequest("POST", baseURL+"/users", nil) + if err != nil { + return nil, err + } + + response, err := client.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + err = json.NewDecoder(response.Body).Decode(&data) + if err != nil { + return nil, err + } + + return data,nil +} +``` + +Statement `&http.Client{}` menghasilkan instance `http.Client`. Objek ini nantinya diperlukan untuk eksekusi request. + +Fungsi `http.NewRequest()` digunakan untuk membuat request baru. Fungsi tersebut memiliki 3 parameter yang wajib diisi. + + 1. Parameter pertama, berisikan tipe request **POST** atau **GET** atau lainnya + 2. Parameter kedua, adalah URL tujuan request + 3. Parameter ketiga, form data request (jika ada) + +Fungsi tersebut menghasilkan instance bertipe `http.Request`. Objek tersebut nantinya disisipkan pada saat eksekusi request. + +Cara eksekusi request sendiri adalah dengan memanggil method `Do()` pada instance `http.Client` yang sudah dibuat, dengan parameter adalah instance request-nya. Contohnya seperti pada `client.Do(request)`. + +Method tersebut mengembalikan instance bertipe `http.Response`, yang didalamnya berisikan informasi yang dikembalikan dari web API. + +Data response bisa diambil lewat property `Body` dalam bentuk string. Gunakan JSON Decoder untuk mengkonversinya menjadi bentuk JSON. Contohnya bisa dilihat di kode di atas, `json.NewDecoder(response.Body).Decode(&data)`. Setelah itu barulah kita bisa menampilkannya. + +Perlu diketahui, data response perlu di-**close** setelah tidak dipakai. Caranya seperti pada kode `defer response.Body.Close()`. + +Selanjutnya, eksekusi fungsi `fetchUsers()` dalam fungsi `main()`. + +```go +func main() { + var users, err = fetchUsers() + if err != nil { + fmt.Println("Error!", err.Error()) + return + } + + for _, each := range users { + fmt.Printf("ID: %s\t Name: %s\t Grade: %d\n", each.ID, each.Name, each.Grade) + } +} +``` + +Jalankan program untuk test hasil. + +![HTTP Request](images/A.52_1_http_request.png) + +## A.52.2. HTTP Request Dengan Form Data + +Untuk menyisipkan data pada sebuah request, ada beberapa hal yang perlu ditambahkan. Yang pertama, import beberapa package lagi, `bytes` dan `net/url`. + +```go +import "bytes" +import "net/url" +``` + +Buat fungsi baru, isinya request ke [http://localhost:8080/user](http://localhost:8080/user) dengan data yang disisipkan adalah `ID`. + +```go +func fetchUser(ID string) (student, error) { + var err error + var client = &http.Client{} + var data student + + var param = url.Values{} + param.Set("id", ID) + var payload = bytes.NewBufferString(param.Encode()) + + request, err := http.NewRequest("POST", baseURL+"/user", payload) + if err != nil { + return data, err + } + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + response, err := client.Do(request) + if err != nil { + return data, err + } + defer response.Body.Close() + + err = json.NewDecoder(response.Body).Decode(&data) + if err != nil { + return data, err + } + + return data, nil +} +``` + +Isi fungsi di atas bisa dilihat memiliki beberapa kemiripan dengan fungsi `fetchUsers()` sebelumnya. + +Statement `url.Values{}` akan menghasilkan objek yang nantinya digunakan sebagai form data request. Pada objek tersebut perlu di set data apa saja yang ingin dikirimkan menggunakan fungsi `Set()` seperti pada `param.Set("id", ID)`. + +Statement `bytes.NewBufferString(param.Encode())` maksudnya, objek form data di-encode lalu diubah menjadi bentuk `bytes.Buffer`, yang nantinya disisipkan pada parameter ketiga pemanggilan fungsi `http.NewRequest()`. + +Karena data yang akan dikirim di-encode, maka pada header perlu di set tipe konten request-nya. Kode `request.Header.Set("Content-Type", "application/x-www-form-urlencoded")` artinya tipe konten request di set sebagai `application/x-www-form-urlencoded`. + +> Pada konteks HTML, HTTP Request yang di trigger dari tag `
` secara default tipe konten-nya sudah di set `application/x-www-form-urlencoded`. Lebih detailnya bisa merujuk ke spesifikasi HTML form [http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1) + +Response dari endpoint `/user` bukanlah slice, tetapi berupa objek. Maka pada saat decode perlu pastikan tipe variabel penampung hasil decode data response adalah `student` (bukan `[]student`). + +Lanjut ke perkodingan, terakhir, implementasikan `fetchUser()` pada fungsi `main()`. + +```go +func main() { + var user1, err = fetchUser("E001") + if err != nil { + fmt.Println("Error!", err.Error()) + return + } + + fmt.Printf("ID: %s\t Name: %s\t Grade: %d\n", user1.ID, user1.Name, user1.Grade) +} +``` + +Untuk keperluan testing, kita hardcode `ID` nilainya `"E001"`. Jalankan program untuk test apakah data yang dikembalikan sesuai. + +![HTTP request Form Data](images/A.52_2_http_request_form_data.png) + +## A.52.3. Secure & Insecure HTTP Request + +Kita telah mempelajari bagaimana cara membuat http request sederhana untuk kirim data dan juga ambil data. Nantinya pada [Bab C.25. Secure & Insecure Client HTTP Request](/C-25-secure-insecure-client-http-request.html) kita akan belajar cara membuat http client request yang lebih njlimet untuk kasus yang lebih advance, tapi sabar dulu hehe. + +--- + + diff --git a/53-sql.md b/53-sql.md new file mode 100644 index 000000000..58f1aa3e0 --- /dev/null +++ b/53-sql.md @@ -0,0 +1,342 @@ +# A.53. SQL + +Go menyediakan package `database/sql` berisikan generic interface untuk keperluan interaksi dengan database sql. Package ini hanya bisa digunakan ketika **driver** database engine yang dipilih juga ada. + +Ada cukup banyak sql driver yang tersedia untuk Go, detailnya bisa diakses di [https://github.com/golang/go/wiki/SQLDrivers](https://github.com/golang/go/wiki/SQLDrivers). Beberapa diantaranya: + + - MySql + - Oracle + - MS Sql Server + - dan lainnya + +Driver-driver tersebut merupakan project open source yang diinisiasi oleh komunitas di Github. Artinya kita selaku developer juga bisa ikut berkontribusi didalamnya. + +Pada bab ini kita akan belajar bagaimana berkomunikasi dengan database MySQL menggunakan driver [Go MySQL Driver](https://github.com/go-sql-driver/mysql). + +## A.53.1. Instalasi Driver + +Unduh driver mysql menggunakan `go get`. + +``` +go get github.com/go-sql-driver/mysql +``` + +![Download mysql driver](images/A.53_1_go_get_driver.png) + +## A.53.2. Setup Database + +> Sebelumnya pastikan sudah ada [mysql server](https://dev.mysql.com/downloads/mysql/) yang terinstal di lokal anda. + +Buat database baru bernama `db_belajar_golang`, dan tabel baru bernama `tb_student`. + +```sql +CREATE TABLE IF NOT EXISTS `tb_student` ( + `id` varchar(5) NOT NULL, + `name` varchar(255) NOT NULL, + `age` int(11) NOT NULL, + `grade` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +INSERT INTO `tb_student` (`id`, `name`, `age`, `grade`) VALUES +('B001', 'Jason Bourne', 29, 1), +('B002', 'James Bond', 27, 1), +('E001', 'Ethan Hunt', 27, 2), +('W001', 'John Wick', 28, 2); + +ALTER TABLE `tb_student` ADD PRIMARY KEY (`id`); +``` + +## A.53.3. Membaca Data Dari MySQL Server + +Import package yang dibutuhkan, lalu disiapkan struct dengan skema yang sama seperti pada tabel `tb_student` di database. Nantinya struct ini digunakan sebagai tipe data penampung hasil query. + +```go +package main + +import "fmt" +import "database/sql" +import _ "github.com/go-sql-driver/mysql" + +type student struct { + id string + name string + age int + grade int +} +``` + +Driver database yang digunakan perlu di-import menggunakan tanda `_`, karena meskipun dibutuhkan oleh package `database/sql`, kita tidak langsung berinteraksi dengan driver tersebut. + +Selanjutnya buat fungsi untuk koneksi ke database. + +```go +func connect() (*sql.DB, error) { + db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/db_belajar_golang") + if err != nil { + return nil, err + } + + return db, nil +} +``` + +Fungsi `sql.Open()` digunakan untuk memulai koneksi dengan database. Fungsi tersebut memiliki 2 parameter mandatory, nama driver dan **connection string**. + +Skema connection string untuk driver mysql yang kita gunakan cukup unik, `root@tcp(127.0.0.1:3306)/db_belajar_golang`. Dibawah ini merupakan skema connection string yang bisa digunakan pada driver Go MySQL Driver. Jika anda menggunakan driver mysql lain, skema koneksinya bisa saja berbeda tergantung driver yang digunakan. +``` +user:password@tcp(host:port)/dbname +user@tcp(host:port)/dbname +``` + +Di bawah ini adalah penjelasan mengenai connection string yang digunakan pada fungsi `connect()`. + +``` +root@tcp(127.0.0.1:3306)/db_belajar_golang +// user => root +// password => +// host => 127.0.0.1 atau localhost +// port => 3306 +// dbname => db_belajar_golang +``` + +Setelah fungsi untuk konektivitas dengan database sudah dibuat, saatnya untuk mempraktekan proses pembacaan data dari server database. Siapkan fungsi `sqlQuery()` dengan isi adalah kode berikut. + +```go +func sqlQuery() { + db, err := connect() + if err != nil { + fmt.Println(err.Error()) + return + } + defer db.Close() + + var age = 27 + rows, err := db.Query("select id, name, grade from tb_student where age = ?", age) + if err != nil { + fmt.Println(err.Error()) + return + } + defer rows.Close() + + var result []student + + for rows.Next() { + var each = student{} + var err = rows.Scan(&each.id, &each.name, &each.grade) + + if err != nil { + fmt.Println(err.Error()) + return + } + + result = append(result, each) + } + + if err = rows.Err(); err != nil { + fmt.Println(err.Error()) + return + } + + for _, each := range result { + fmt.Println(each.name) + } +} +``` + +Setiap kali terbuat koneksi baru, jangan lupa untuk selalu **close** instance koneksinya. Bisa menggunakan keyword `defer` seperti pada kode di atas, `defer db.Close()`. + +Fungsi `db.Query()` digunakan untuk eksekusi sql query. Fungsi tersebut parameter keduanya adalah variadic, sehingga boleh tidak diisi. Pada kode di atas bisa dilihat bahwa nilai salah satu clause `where` adalah tanda tanya (`?`). Tanda tersebut kemudian akan ter-replace oleh nilai pada parameter setelahnya (nilai variabel `age`). Teknik penulisan query sejenis ini sangat dianjurkan, untuk mencegah [sql injection](https://en.wikipedia.org/wiki/SQL_injection). + +Fungsi tersebut menghasilkan instance bertipe `sql.*Rows`, yang juga perlu di **close** ketika sudah tidak digunakan (`defer rows.Close()`). + +Selanjutnya, sebuah array dengan tipe elemen struct `student` disiapkan dengan nama `result`. Nantinya hasil query akan ditampung ke variabel tersebut. + +Kemudian dilakukan perulangan dengan acuan kondisi adalah `rows.Next()`. Perulangan dengan cara ini dilakukan sebanyak jumlah total record yang ada, berurutan dari record pertama hingga akhir, satu per satu. + +Method `Scan()` milik `sql.Rows` berfungsi untuk mengambil nilai record yang sedang diiterasi, untuk disimpan pada variabel pointer. Variabel yang digunakan untuk menyimpan field-field record dituliskan berurutan sebagai parameter variadic, sesuai dengan field yang di select pada query. Silakan lihat perbandingan dibawah ini unuk lebih jelasnya. + +``` +// query +select id, name, grade ... + +// scan +rows.Scan(&each.id, &each.name, &each.grade ... +``` + +Data record yang didapat kemudian di-append ke slice `result`, lewat statement `result = append(result, each)`. + +OK, sekarang tinggal panggil fungsi `sqlQuery()` di `main`, lalu jalankan program. + +```go +func main() { + sqlQuery() +} +``` + +Output: + +![Membaca data dari database server](images/A.53_2_sql_query.png) + +## A.53.4. Membaca 1 Record Data Menggunakan Method `QueryRow()` + +Untuk query yang menghasilkan 1 baris record saja, bisa gunakan method `QueryRow()`, dengan metode ini kode menjadi lebih ringkas. Chain dengan method `Scan()` untuk mendapatkan value-nya. + +```go +func sqlQueryRow() { + var db, err = connect() + if err != nil { + fmt.Println(err.Error()) + return + } + defer db.Close() + + var result = student{} + var id = "E001" + err = db. + QueryRow("select name, grade from tb_student where id = ?", id). + Scan(&result.name, &result.grade) + if err != nil { + fmt.Println(err.Error()) + return + } + + fmt.Printf("name: %s\ngrade: %d\n", result.name, result.grade) +} + +func main() { + sqlQueryRow() +} +``` + +Dari kode di atas ada statement yang dituliskan cukup unik, chain statement boleh dituliskan dalam beberapa baris, contohnya: + +```go +err = db. + QueryRow("select name, grade from tb_student where id = ?", id). + Scan(&result.name, &result.grade) +``` + +Sekarang jalankan program. Outputnya akan muncul data record sesuai id. + +![Penggunaan `QueryRow()`](images/A.53_3_sql_query_row.png) + +## A.53.5. Eksekusi Query Menggunakan `Prepare()` + +Teknik **prepared statement** adalah teknik penulisan query di awal dengan kelebihan bisa di re-use atau digunakan banyak kali untuk eksekusi yang berbeda-beda. + +Metode ini bisa digabung dengan `Query()` maupun `QueryRow()`. Berikut merupakan contoh penerapannya. + +```go +func sqlPrepare() { + db, err := connect() + if err != nil { + fmt.Println(err.Error()) + return + } + defer db.Close() + + stmt, err := db.Prepare("select name, grade from tb_student where id = ?") + if err != nil { + fmt.Println(err.Error()) + return + } + + var result1 = student{} + stmt.QueryRow("E001").Scan(&result1.name, &result1.grade) + fmt.Printf("name: %s\ngrade: %d\n", result1.name, result1.grade) + + var result2 = student{} + stmt.QueryRow("W001").Scan(&result2.name, &result2.grade) + fmt.Printf("name: %s\ngrade: %d\n", result2.name, result2.grade) + + var result3 = student{} + stmt.QueryRow("B001").Scan(&result3.name, &result3.grade) + fmt.Printf("name: %s\ngrade: %d\n", result3.name, result3.grade) +} + +func main() { + sqlPrepare() +} +``` + +Method `Prepare()` digunakan untuk deklarasi query, yang mengembalikan objek bertipe `sql.*Stmt`. Dari objek tersebut, dipanggil method `QueryRow()` beberapa kali dengan isi value untuk `id` berbeda-beda untuk tiap pemanggilannya. + +![Prepared statement](images/A.53_4_prepared_statement.png) + +## A.53.6. Insert, Update, & Delete Data Menggunakan `Exec()` + +Untuk operasi **insert**, **update**, dan **delete**; dianjurkan untuk tidak menggunakan fungsi `sql.Query()` ataupun `sql.QueryRow()` untuk eksekusinya. Direkomendasikan eksekusi perintah-perintah tersebut lewat fungsi `Exec()`, contohnya seperti pada kode berikut. + +```go +func sqlExec() { + db, err := connect() + if err != nil { + fmt.Println(err.Error()) + return + } + defer db.Close() + + _, err = db.Exec("insert into tb_student values (?, ?, ?, ?)", "G001", "Galahad", 29, 2) + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println("insert success!") + + _, err = db.Exec("update tb_student set age = ? where id = ?", 28, "G001") + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println("update success!") + + _, err = db.Exec("delete from tb_student where id = ?", "G001") + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println("delete success!") +} + +func main() { + sqlExec() +} +``` + +Teknik prepared statement juga bisa digunakan pada metode ini. Berikut adalah perbandingan eksekusi `Exec()` menggunakan `Prepare()` dan cara biasa. + +```go +// menggunakan metode prepared statement +stmt, err := db.Prepare("insert into tb_student values (?, ?, ?, ?)") +stmt.Exec("G001", "Galahad", 29, 2) + +// menggunakan metode biasa +_, err := db.Exec("insert into tb_student values (?, ?, ?, ?)", "G001", "Galahad", 29, 2) +``` + +## A.53.7. Koneksi Dengan Engine Database Lain + +Karena package `database/sql` merupakan interface generic, maka cara untuk koneksi ke engine database lain (semisal Oracle, Postgres, SQL Server) adalah sama dengan cara koneksi ke MySQL. Cukup dengan meng-import driver yang digunakan, lalu mengganti nama driver pada saat pembuatan koneksi baru. + +```go +sql.Open(driverName, connectionString) +``` + +Sebagai contoh saya menggunakan driver [pq](https://github.com/lib/pq) untuk koneksi ke server Postgres, maka connection string-nya: + +```go +sql.Open("pq", "user=postgres password=secret dbname=test sslmode=disable") +``` + +Selengkapya mengenai driver yang tersedia bisa dilihat di [https://github.com/golang/go/wiki/SQLDrivers](https://github.com/golang/go/wiki/SQLDrivers). + +--- + +- [Go MySQL Driver](https://github.com/go-sql-driver/mysql), by Julien Schmidt, MPL-2.0 license + +--- + + diff --git a/54-mongodb.md b/54-mongodb.md new file mode 100644 index 000000000..1398627cf --- /dev/null +++ b/54-mongodb.md @@ -0,0 +1,331 @@ +# A.54. NoSQL MongoDB + +Go tidak menyediakan interface generic untuk NoSQL, jadi implementasi driver tiap brand NoSQL di Go biasanya berbeda satu dengan lainnya. + +Pada bab ini kita akan belajar cara berkomunikasi dengan NoSQL MongoDB server menggunakan official driver untuk go, yaitu [mongo-go-driver](https://github.com/mongodb/mongo-go-driver). + +## A.54.1. Persiapan + +Ada beberapa hal yang perlu disiapkan sebelum mulai masuk ke bagian coding. + + 1. Instal mongo-go-driver menggunakan `go get`. + + ``` + go get https://github.com/mongodb/mongo-go-driver + ``` + + 2. Pastikan sudah terinstal MongoDB di komputer anda, dan jangan lupa untuk menjalankan daemon-nya. Jika belum, [download](ihttps://www.mongodb.org/downloads) dan install terlebih dahulu. + + 3. Instal juga MongoDB GUI untuk mempermudah browsing data. Bisa menggunakan [MongoChef](http://3t.io/mongochef/), [Robomongo](http://robomongo.org/), atau lainnya. + +## A.54.2. Insert Data + +Cara insert data ke mongodb via Go tidak terlalu sulit. Kita akan mempelajarinya dengan cara praktek langsung. Pertama-tama silakan import package yang dibutuhkan. + +```go +package main + +import ( + "context" + "fmt" + "log" + "time" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/bson" +) +``` + +Siapkan satu object context dan struct `student`. Rencananya satu buah document kita buat sebagai satu buah objek `student`. + +Perlu diketahui bahwa pada bab ini tidak dijelaskan tentang apa itu context. Silakan merujuk ke [Bab C.31. Context: Value, Timeout, & Cancellation](/C-31-golang-context.html) untuk mempelajarinya. Menggunakan satu context background untuk semua operasi sangat tidak dianjurkan, tapi pada bab ini kita terapkan demikian agar tidak menambah kebingungan pembaca yang masih proses belajar. Context sendiri fungsinya sangat banyak, untuk kasus sejenis biasanya digunakan untuk handle operation timeout atau lainnya. + +```go +var ctx = context.Background() + +type student struct { + Name string `bson:"name"` + Grade int `bson:"Grade"` +} +``` + +Tag `bson` pada property struct digunakan sebagai penentu nama field ketika data disimpan ke dalam collection. Jika sebuah property tidak memiliki tag bson, secara default nama field adalah sama dengan nama property hanya saja lowercase. Untuk customize nama field, gunakan tag `bson`. + +Pada contoh di atas, property `Name` ditentukan nama field mongo-nya sebagai `name`, dan `Grade` sebagai `Grade`. + +Selanjutnya siapkan fungsi untuk membuat satu buah mongo connection. Dari objek connection diambil object database, kemudian dijadikan sebagai nilai balik fungsi. + +```go +func connect() (*mongo.Database, error) { + clientOptions := options.Client() + clientOptions.ApplyURI("mongodb://localhost:27017") + client, err := mongo.NewClient(client) + if err != nil { + return nil, err + } + + err = client.Connect(ctx) + if err != nil { + return nil, err + } + + return client.Database("belajar_golang"), nil +} +``` + +Fungsi `mongo.NewClient()` digunakan untuk meng-inisialisasi koneksi database dari client ke server. Fungsi tersebut memerlukan parameter bertipe `*options.ClientOptions`. Pada client options mongo connection string perlu di set (lewat method `.ApplyURI()`). + +> Silakan sesuaikan connection string dengan mongo db server yang dipergunakan. Lebih jelasnya silakan merujuk ke [MongoDB Documentation: Connection String URI Format](https://docs.mongodb.com/manual/reference/connection-string/). + +Dari object client, panggil method `.Connect()` untuk inisialisasi koneksi ke db server. Setelah itu panggil method `.Database()` untuk set database yang aktif. + +Lanjut buat fungsi yang didalamnya berisikan kode untuk insert data ke mongodb, lalu panggil fungsi tersebut di `main()`. + +```go +func insert() { + db, err := connect() + if err != nil { + log.Fatal(err.Error()) + } + + _, err = db.Collection("student").InsertOne(ctx, student{"Wick", 2}) + if err != nil { + log.Fatal(err.Error()) + } + + _, err = db.Collection("student").InsertOne(ctx, student{"Ethan", 2}) + if err != nil { + log.Fatal(err.Error()) + } + + fmt.Println("Insert success!") +} + +func main() { + insert() +} +``` + +Fungsi `connect()` mengembalikan objek bertipe `*mongo.Database`. Dari objek tersebut akses method `.Collection()` lalu chain dengan method lainnya untuk melakukan operasi database, kurang lebih skema statement-nya sama seperti operasi mongodb. + +Sebagai contoh, pada kode di atas `.InsertOne()` digunakan untuk insert satu data ke database. Perbandingannya kurang lebih seperti berikut: + +```js +// mongodb +db.getCollection("student").insertOne({ name: "Wick", Grade: 2 }) + +// mongo-do-driver +db.Collection("student").InsertOne(ctx, student{ name: "Wick", Grade: 2}) +``` + +Perlu diketahui, bahwa di mongo-go-driver setiap operasi biasanya membutuhkan objek context untuk disisipkan sebagai parameter pertama. Pada contoh di atas kita gunakan variabel `ctx` yang sudah dideklarasikan sebelumnya. + +![Insert mongo](images/A.54_2_insert.png) + +## A.54.3. Membaca Data + +Method `.Find()` digunakan untuk membaca atau mencari data. Method ini mengembalikan objek cursor, objek ini harus digunakan dalam perulangan untuk mengambil data yang ditemukan. + +Dalam pencarian, sisipkan query atau filter sebagai parameter ke-dua method `.Find()`. + +```go +func find() { + db, err := connect() + if err != nil { + log.Fatal(err.Error()) + } + + csr, err := db.Collection("student").Find(ctx, bson.M{"name": "Wick"}) + if err != nil { + log.Fatal(err.Error()) + } + defer csr.Close(ctx) + + result := make([]student, 0) + for csr.Next(ctx) { + var row student + err := csr.Decode(&row) + if err != nil { + log.Fatal(err.Error()) + } + + result = append(result, row) + } + + if len(result) > 0 { + fmt.Println("Name :", result[0].Name) + fmt.Println("Grade :", result[0].Grade) + } +} + +func main() { + find() +} +``` + +Query selector ditulis dalam tipe `bson.M`. Tipe ini sebenarnya adalah alias dari `map[string]interface{}`. + +Cara untuk mendapatkan semua rows hasil pencarian kursor adalah dengan mengiterasi method `.Next()` dengan didalamnya method `.Decode()` dipanggil untuk retrieve datanya. Setelah itu data yang sudah terampil di-append ke slice. + +Selain method `.Find()` ada juga `.FindOne()`, silakan cek dokumentasi lebih jelasnya. + +![Pencarian data](images/A.54_3_find.png) + +Berikut adalah skema perbandingan contoh operasi get data menggunakan mongo query vs mongo-go-driver: + +```js +// mongodb +db.getCollection("student").find({"name": "Wick"}) + +// mongo-do-driver +db.Collection("student").Find(ctx, bson.M{"name": "Wick"}) +``` + +## A.54.4. Update Data + +Method `.Update()` digunakan untuk update data (jika update hanya diinginkan untuk berlaku pada 1 dokumen saja, maka gunakan `.UpdateOne()`). Method `.Update()` memerlukan 3 buah parameter dalam pemanggilannya. + + 1. Parameter pertama, objek context + 2. Parameter kedua adalah query kondisi yang mengacu ke data mana yang ingin di update + 3. Parameter ketiga adalah perubahan datanya. + +Di bawah ini adalah contok implementasi method `Update()`. + +```go +func update() { + db, err := connect() + if err != nil { + log.Fatal(err.Error()) + } + + var selector = bson.M{"name": "Wick"} + var changes = student{"John Wick", 2} + _, err = db.Collection("student").UpdateOne(ctx, selector, bson.M{"$set": changes}) + if err != nil { + log.Fatal(err.Error()) + } + + fmt.Println("Update success!") +} + +func main() { + update() +} +``` + +Jalankan kode di atas, lalu cek lewat Mongo GUI apakah data berubah. + +![Update data](images/A.54_4_update.png) + +Berikut adalah skema perbandingan query vs mongo-go-driver dari operasi di atas. + +```js +// mongodb +db.getCollection("student").update({"name": "Wick"}, { "$set": {"name": "Wick", "Grade": 2} }) + +// mongo-do-driver +db.Collection("student").UpdateOne(ctx, bson.M{"name": "Wick"}, bson.M{"$set": student{"John Wick", 2}}) +``` + +Selain method `.UpdateOne()` ada juga method `.UpdateMany()`, kegunaan masing-masing bisa dilihat dari nama fungsinya. + +## A.54.5. Menghapus Data + +Untuk menghapus data gunakan method `.DeleteOne()` atau `.DeleteMany()`. + +```go +func remove() { + db, err := connect() + if err != nil { + log.Fatal(err.Error()) + } + + var selector = bson.M{"name": "John Wick"} + _, err = db.Collection("student").DeleteOne(ctx, selector) + if err != nil { + log.Fatal(err.Error()) + } + + fmt.Println("Remove success!") +} + +func main() { + remove() +} +``` + +Hasil dari kode di atas, 2 data yang sebelumnya sudah di-insert kini tinggal satu saja. + +![Menghapus data](images/A.54_5_remove.png) + +Berikut adalah skema perbandingan query vs mongo-go-driver dari operasi di atas. + +```js +// mongodb +db.getCollection("student").delete({"name": "John Wick"}) + +// mongo-do-driver +db.Collection("student").DeleteMany(ctx, bson.M{"name": "John Wick"}) +``` + +## A.54.6. Aggregate Data + +Agregasi data menggunakan driver ini juga cukup mudah, caranya tinggal gunakan method `.Aggregate()` dan sisipkan pipeline query sebagai argument ke-2 pemanggilan method. Eksekusi method tersebut mengembalikan objek cursor. Selebihnya capture result dengan cara yang sama seperti capture cursor operasi `.Find()`. + +Pipeline sendiri bisa dituliskan langsung dalam `[]bson.M`, atau bisa tulis dalam bentuk string dan unmarshal ke `[]bson.M`. + +```go +pipeline := make([]bson.M, 0) +err = bson.UnmarshalExtJSON([]byte(strings.TrimSpace(` + [ + { "$group": { + "_id": null, + "Total": { "$sum": 1 } + } }, + { "$project": { + "Total": 1, + "_id": 0 + } } + ] +`)), true, &pipeline) +if err != nil { + log.Fatal(err.Error()) +} +``` + +Pada kode lanjutan berikut, method `.Aggregate()` dipanggil dan disisipkan pipeline-nya. + +```go +csr, err := db.Collection("student").Aggregate(ctx, pipeline) +if err != nil { + log.Fatal(err.Error()) +} +defer csr.Close(ctx) + +result := make([]bson.M, 0) +for csr.Next(ctx) { + var row bson.M + err := csr.Decode(&row) + if err != nil { + log.Fatal(err.Error()) + } + + result = append(result, row) +} + +if len(result) > 0 { + fmt.Println("Total :", result[0]["Total"]) +} +``` + +--- + +- [Mongo Go Driver](https://github.com/mongodb/mongo-go-driver), by MongoDB Team, Apache-2.0 license + +--- + + diff --git a/55-unit-test.md b/55-unit-test.md new file mode 100644 index 000000000..7deff5e7d --- /dev/null +++ b/55-unit-test.md @@ -0,0 +1,194 @@ +# A.55. Unit Test + +Go menyediakan package `testing`, berisikan banyak sekali tools untuk keperluan unit test. + +Pada bab ini kita akan belajar mengenai testing, benchmark, dan juga testing menggunakan [testify](https://github.com/stretchr/testify). + +## A.55.1. Persiapan + +Langsung saja kita praktek. Pertama siapkan terlebih dahulu sebuah struct `Kubus`. Variabel object hasil struct ini nantinya kita gunakan sebagai bahan testing. + +```go +package main + +import "math" + +type Kubus struct { + Sisi float64 +} + +func (k Kubus) Volume() float64 { + return math.Pow(k.Sisi, 3) +} + +func (k Kubus) Luas() float64 { + return math.Pow(k.Sisi, 2) * 6 +} + +func (k Kubus) Keliling() float64 { + return k.Sisi * 12 +} +``` + +Simpan kode di atas dengan nama `bab55.go`. + +## A.55.2. Testing + +File untuk keperluan testing dipisah dengan file utama, namanya harus berakhiran `_test.go`, dan package-nya harus sama. Pada bab ini, file utama adalah `bab55.go`, maka file testing harus bernama `bab55_test.go`. + +Unit test di Go dituliskan dalam bentuk fungsi, yang memiliki parameter yang bertipe `*testing.T`, dengan nama fungsi harus diawali kata **Test** (pastikan sudah meng-import package `testing` sebelumnya). Lewat parameter tersebut, kita bisa mengakses method-method untuk keperluan testing. + +Pada contoh di bawah ini disiapkan 3 buah fungsi test, yang masing-masing digunakan untuk mengecek apakah hasil kalkulasi volume, luas, dan keliling kubus adalah benar. + +```go +package main + +import "testing" + +var ( + kubus Kubus = Kubus{4} + volumeSeharusnya float64 = 64 + luasSeharusnya float64 = 96 + kelilingSeharusnya float64 = 48 +) + +func TestHitungVolume(t *testing.T) { + t.Logf("Volume : %.2f", kubus.Volume()) + + if kubus.Volume() != volumeSeharusnya { + t.Errorf("SALAH! harusnya %.2f", volumeSeharusnya) + } +} + +func TestHitungLuas(t *testing.T) { + t.Logf("Luas : %.2f", kubus.Luas()) + + if kubus.Luas() != luasSeharusnya { + t.Errorf("SALAH! harusnya %.2f", luasSeharusnya) + } +} + +func TestHitungKeliling(t *testing.T) { + t.Logf("Keliling : %.2f", kubus.Keliling()) + + if kubus.Keliling() != kelilingSeharusnya { + t.Errorf("SALAH! harusnya %.2f", kelilingSeharusnya) + } +} +``` + +Method `t.Logf()` digunakan untuk memunculkan log. Method ini equivalen dengan `fmt.Printf()`. + +Method `Errorf()` digunakan untuk memunculkan log dengan diikuti keterangan bahwa terjadi **fail** pada saat testing. + +Cara eksekusi testing adalah menggunakan command `go test`. Karena struct yang diuji berada dalam file `bab55.go`, maka pada saat eksekusi test menggunakan `go test`, nama file `bab55_test.go` dan `bab55.go` perlu dituliskan sebagai argument. + +Argument `-v` atau verbose digunakan menampilkan semua output log pada saat pengujian. + +Jalankan aplikasi seperti gambar dibawah ini, terlihat bahwa tidak ada test yang fail. + +![Testing](images/A.55_1_test.png) + +OK, selanjutnya coba ubah rumus kalkulasi method `Keliling()`. Tujuan dari pengubahan ini adalah untuk mengetahui bagaimana penanda fail muncul ketika ada test yang gagal. + +```go +func (k Kubus) Keliling() float64 { + return k.Sisi * 15 +} +``` + +Setelah itu jalankan lagi test. + +![Test fail](images/A.55_2_test_fail.png) + +## A.55.3. Method Test + +Table berikut berisikan method standar testing yang bisa digunakan di Go. + +| Method | Kegunaan | +| :----- | :------- | +| `Log()` | Menampilkan log | +| `Logf()` | Menampilkan log menggunakan format | +| `Fail()` | Menandakan terjadi `Fail()` dan proses testing fungsi tetap diteruskan | +| `FailNow()` | Menandakan terjadi `Fail()` dan proses testing fungsi dihentikan | +| `Failed()` | Menampilkan laporan fail | +| `Error()` | `Log()` diikuti dengan `Fail()` | +| `Errorf()` | `Logf()` diikuti dengan `Fail()` | +| `Fatal()` | `Log()` diikuti dengan `failNow()` | +| `Fatalf()` | `Logf()` diikuti dengan `failNow()` | +| `Skip()` | `Log()` diikuti dengan `SkipNow()` | +| `Skipf()` | `Logf()` diikuti dengan `SkipNow()` | +| `SkipNow()` | Menghentikan proses testing fungsi, dilanjutkan ke testing fungsi setelahnya | +| `Skiped()` | Menampilkan laporan skip | +| `Parallel()` | Menge-set bahwa eksekusi testing adalah parallel | + +## A.55.4. Benchmark + +Package `testing` selain berisikan tools untuk testing juga berisikan tools untuk benchmarking. Cara pembuatan benchmark sendiri cukup mudah yaitu dengan membuat fungsi yang namanya diawali dengan **Benchmark** dan parameternya bertipe `*testing.B`. + +Sebagai contoh, kita akan mengetes performa perhitungan luas kubus. Siapkan fungsi dengan nama `BenchmarkHitungLuas()` dengan isi adalah kode berikut. + +```go +func BenchmarkHitungLuas(b *testing.B) { + for i := 0; i < b.N; i++ { + kubus.Luas() + } +} +``` + +Jalankan test menggunakan argument `-bench=.`, argumen ini digunakan untuk menandai bahwa selain testing terdapat juga benchmark yang perlu diuji. + +![Benchmark](images/A.55_3_benchmark.png) + +Arti dari `30000000 51.1 ns/op` adalah, fungsi di atas di-test sebanyak **30 juta** kali, hasilnya membutuhkan waktu rata-rata **51 nano detik** untuk run satu fungsi. + +## A.55.5. Testing Menggunakan testify + +Package **testify** berisikan banyak sekali tools yang bisa dimanfaatkan untuk keperluan testing di Go. + +Testify bisa di-download pada [github.com/stretchr/testify](https://github.com/stretchr/testify) menggunakan `go get`. + +Didalam testify terdapat 5 package dengan kegunaan berbeda-beda satu dengan lainnya. Detailnya bisa dilihat pada tabel berikut. + +| Package | Kegunaan | +| :------ | :------- | +| assert | Berisikan tools standar untuk testing | +| http | Berisikan tools untuk keperluan testing http | +| mock | Berisikan tools untuk mocking object | +| require | Sama seperti assert, hanya saja jika terjadi fail pada saat test akan menghentikan eksekusi program | +| suite | Berisikan tools testing yang berhubungan dengan struct dan method | + +Pada bab ini akan kita contohkan bagaimana penggunaan package assert. Silakan perhatikan contoh berikut. + +```go +import "github.com/stretchr/testify/assert" + +... + +func TestHitungVolume(t *testing.T) { + assert.Equal(t, kubus.Volume(), volumeSeharusnya, "perhitungan volume salah") +} + +func TestHitungLuas(t *testing.T) { + assert.Equal(t, kubus.Luas(), luasSeharusnya, "perhitungan luas salah") +} + +func TestHitungKeliling(t *testing.T) { + assert.Equal(t, kubus.Keliling(), kelilingSeharusnya, "perhitungan keliling salah") +} +``` + +Fungsi `assert.Equal()` digunakan untuk uji perbandingan. Parameter ke-2 dibandingkan nilainya dengan parameter ke-3. Jika tidak sama, maka pesan parameter ke-3 akan dimunculkan. + +![Testing menggunakan testify](images/A.55_4_testify.png) + +--- + +- [Testify](https://github.com/stretchr/testify), by "Stretchr, Inc" + +--- + + diff --git a/56-waitgroup.md b/56-waitgroup.md new file mode 100644 index 000000000..9159fdea6 --- /dev/null +++ b/56-waitgroup.md @@ -0,0 +1,75 @@ +# A.56. WaitGroup + +Sebelumnya kita telah belajar banyak mengenai channel, yang fungsi utama-nya adalah untuk sharing data antar goroutine. Selain untuk komunikasi data, channel secara tidak langsung bisa dimanfaatkan untuk kontrol goroutine. + +Go menyediakan package `sync`, berisi cukup banyak API untuk handle masalah multiprocessing (goroutine), salah satunya diantaranya adalah yang kita bahas di bab ini, yaitu `sync.WaitGroup`. + +Kegunaan `sync.WaitGroup` adalah untuk sinkronisasi goroutine. Berbeda dengan channel, `sync.WaitGroup` memang didesain khusus untuk maintain goroutine, penggunaannya lebih mudah dan lebih efektif dibanding channel. + +> Sebenarnya kurang pas jika membandingkan `sync.WaitGroup` dan channel, karena fungsi utama dari keduanya adalah berbeda. Channel untuk keperluan sharing data antar goroutine, sedangkan `sync.WaitGroup` untuk sinkronisasi goroutine. + +## A.56.1. Penerapan `sync.WaitGroup` + +`sync.WaitGroup` digunakan untuk menunggu goroutine. Cara penggunaannya sangat mudah, tinggal masukan jumlah goroutine yang dieksekusi, sebagai parameter method `Add()` object cetakan `sync.WaitGroup`. Dan pada akhir tiap-tiap goroutine, panggil method `Done()`. Juga, pada baris kode setelah eksekusi goroutine, panggil method `Wait()`. + +Agar lebih jelas, silakan coba kode berikut. + +```go +package main + +import "sync" +import "runtime" +import "fmt" + +func doPrint(wg *sync.WaitGroup, message string) { + defer wg.Done() + fmt.Println(message) +} + +func main() { + runtime.GOMAXPROCS(2) + + var wg sync.WaitGroup + + for i := 0; i < 5; i++ { + var data = fmt.Sprintf("data %d", i) + + wg.Add(1) + go doPrint(&wg, data) + } + + wg.Wait() +} +``` + +Kode di atas merupakan contoh penerapan `sync.WaitGroup` untuk pengelolahan goroutine. Fungsi `doPrint()` akan dijalankan sebagai goroutine, dengan tugas menampilkan isi variabel `message`. + +Variabel `wg` disiapkan bertipe `sync.WaitGroup`, dibuat untuk sinkronisasi goroutines yang dijalankan. + +Di tiap perulangan statement `wg.Add(1)` dipanggil. Kode tersebut akan memberikan informasi kepada `wg` bahwa jumlah goroutine yang sedang diproses ditambah 1 (karena dipanggil 5 kali, maka `wg` akan sadar bahwa terdapat 5 buah goroutine sedang berjalan). + +Di baris selanjutnya, fungsi `doPrint()` dieksekusi sebagai goroutine. Didalam fungsi tersebut, sebuah method bernama `Done()` dipanggil. Method ini digunakan untuk memberikan informasi kepada `wg` bahwa goroutine dimana method itu dipanggil sudah selesai. Sejumlah 5 buah goroutine dijalankan, maka method tersebut harus dipanggil 5 kali. + +Statement `wg.Wait()` bersifat blocking, proses eksekusi program tidak akan diteruskan ke baris selanjutnya, sebelum sejumlah 5 goroutine selesai. Jika `Add(1)` dipanggil 5 kali, maka `Done()` juga harus dipanggil 5 kali. + +Output program di atas. + +![Contoh penerapan `sync.WaitGroup`](images/A.56_1_waitgroup.png) + +## A.56.2. Perbedaan WaitGroup Dengan Channel + +Bukan sebuah perbandingan yang valid, tapi jika dibandingkan maka perbedaan antara channel dan `sync.WaitGroup` kurang lebih sebagai berikut: + + - Channel tergantung kepada goroutine tertentu dalam penggunaannya, tidak seperti `sync.WaitGroup` yang dia tidak perlu tahu goroutine mana saja yang dijalankan, cukup tahu jumlah goroutine yang harus selesai + - Penerapan `sync.WaitGroup` lebih mudah dibanding channel + - Kegunaan utama channel adalah untuk komunikasi data antar goroutine. Sifatnya yang blocking bisa kita manfaatkan untuk manage goroutine; sedangkan WaitGroup khusus digunakan untuk sinkronisasi goroutine + - Performa `sync.WaitGroup` lebih baik dibanding channel, sumber: https://groups.google.com/forum/#!topic/golang-nuts/whpCEk9yLhc + +Kombinasi yang tepat antara `sync.WaitGroup` dan channel sangat penting, keduanya diperlukan dalam concurrent process agar bisa maksimal. + +--- + + diff --git a/57-mutex.md b/57-mutex.md new file mode 100644 index 000000000..9fd731082 --- /dev/null +++ b/57-mutex.md @@ -0,0 +1,175 @@ +# A.57. Mutex + +Sebelum kita membahas mengenai apa itu **mutex**? ada baiknya untuk mempelajari terlebih dahulu apa itu **race condition**, karena kedua konsep ini berhubungan erat satu sama lain. + +Race condition adalah kondisi dimana lebih dari satu goroutine, mengakses data yang sama pada waktu yang bersamaan (benar-benar bersamaan). Ketika hal ini terjadi, nilai data tersebut akan menjadi kacau. Dalam **concurrency programming** situasi seperti ini ini sering terjadi. + +Mutex melakukan pengubahan level akses sebuah data menjadi eksklusif, menjadikan data tersebut hanya dapat dikonsumsi (read / write) oleh satu buah goroutine saja. Ketika terjadi race condition, maka hanya goroutine yang beruntung saja yang bisa mengakses data tersebut. Goroutine lain (yang waktu running nya kebetulan bersamaan) akan dipaksa untuk menunggu, hingga goroutine yang sedang memanfaatkan data tersebut selesai. + +Go menyediakan `sync.Mutex` yang bisa dimanfaatkan untuk keperluan **lock** dan **unlock** data. Pada bab ini kita akan membahas mengenai race condition, dan menanggulanginya menggunakan mutex. + +## A.57.1. Persiapan + +Pertama siapkan struct baru bernama `counter`, dengan isi satu buah property `val` bertipe `int`. Property ini nantinya dikonsumsi dan diolah oleh banyak goroutine. + +Lalu buat beberapa method struct `counter`. + + 1. Method `Add()`, untuk increment nilai. + 2. Method `Value()`, untuk mengembalikan nilai. + +```go +package main + +import ( + "fmt" + "runtime" + "sync" +) + +type counter struct { + val int +} + +func (c *counter) Add(x int) { + c.val++ +} + +func (c *counter) Value() (x int) { + return c.val +} +``` + +Kode di atas kita gunakan sebagai template contoh source code yang ada pada bab ini. + +## A.57.2. Contoh Race Condition + +Program berikut merupakan contoh program yang didalamnya memungkinkan terjadi race condition atau kondisi goroutine balapan. + +> Pastikan jumlah core prosesor komputer anda adalah lebih dari satu. Karena contoh pada bab ini hanya akan berjalan sesuai harapan jika `GOMAXPROCS` > 1. + +```go +func main() { + runtime.GOMAXPROCS(2) + + var wg sync.WaitGroup + var meter counter + + for i := 0; i < 1000; i++ { + wg.Add(1) + + go func() { + for j := 0; j < 1000; j++ { + meter.Add(1) + } + + wg.Done() + }() + } + + wg.Wait() + fmt.Println(meter.Value()) +} +``` + +Pada kode diatas, disiapkan sebuah instance `sync.WaitGroup` bernama `wg`, dan variabel object `meter` bertipe `counter` (nilai property `val` default-nya adalah **0**). + +Setelahnya dijalankan perulangan sebanyak 1000 kali, yang ditiap perulanganya dijalankan sebuah goroutine baru. Didalam goroutine tersebut, terdapat perulangan lagi, sebanyak 1000 kali. Dalam perulangan tersebut nilai property `val` dinaikkan sebanyak 1 lewat method `Add()`. + +Dengan demikian, ekspektasi nilai akhir `meter.val` harusnya adalah 1000000. + +Di akhir, `wg.Wait()` dipanggil, dan nilai variabel counter `meter` diambil lewat `meter.Value()` untuk kemudian ditampilkan. + +Jalankan program, lihat hasilnya. + +![Contoh race condition](images/A.57_1_race_condition.png) + +Nilai `meter.val` tidak genap 1000000? kenapa bisa begitu? Padahal seharusnya tidak ada masalah dalam kode yang kita tulis di atas. + +Inilah yang disebut dengan race condition, data yang diakses bersamaan dalam 1 waktu menjadi kacau. + +## A.57.3. Deteksi Race Condition Menggunakan Go Race Detector + +Go menyediakan fitur untuk [deteksi race condition](http://blog.golang.org/race-detector). Cara penggunaannya adalah dengan menambahkan flag `-race` pada saat eksekusi aplikasi. + +![Race detector](images/A.57_2_race_detector.png) + +Terlihat pada gambar diatas, ada pesan memberitahu terdapat kemungkinan data race pada program yang kita jalankan. + +## A.57.4. Penerapan `sync.Mutex` + +Sekarang kita tahu bahwa program di atas menghasilkan bug, ada kemungkinan data race didalamnya. Untuk mengatasi masalah ini ada beberapa cara yang bisa digunakan, dan disini kita akan menggunakan `sync.Mutex`. + +Ubah kode di atas, embed struct `sync.Mutex` kedalam struct `counter`, agar lewat objek cetakan `counter` kita bisa melakukan lock dan unlock dengan mudah. Tambahkan method `Lock()` dan `Unlock()` didalam method `Add()`. + +```go +type counter struct { + sync.Mutex + val int +} + +func (c *counter) Add(x int) { + c.Lock() + c.val++ + c.Unlock() +} + +func (c *counter) Value() (x int) { + return c.val +} +``` + +Method `Lock()` digunakan untuk menandai bahwa semua operasi pada baris setelah kode tersebut adalah bersifat eksklusif. Hanya ada satu buah goroutine yang bisa melakukannya dalam satu waktu. Jika ada banyak goroutine yang eksekusinya bersamaan, harus antri. + +Pada kode di atas terdapat kode untuk increment nilai `meter.val`. Maka property tersebut hanya bisa diakses oleh satu goroutine saja. + +Method `Unlock()` akan membuka kembali akses operasi ke property/variabel yang di lock, proses mutual exclusion terjadi diantara method `Lock()` dan `Unlock()`. + +Di contoh di atas, pada saat bagian pengambilan nilai, mutex tidak dipasang, karena kebetulan pengambilan nilai terjadi setelah semua goroutine selesai. Data Race bisa terjadi saat pengubahan maupun pengambilan data, jadi penggunaan mutex harus disesuaikan dengan kasus. + +Coba jalankan program, dan lihat hasilnya. + +![Mutex](images/A.57_3_mutex.png) + +Pada contoh di atas, mutex diterapkan dengan cara di-embed ke objek yang memerlukan proses lock-unlock, menjadikan variabel mutex tersebut adalah eksklusif untuk objek tersebut saja. Cara ini merupakan cara yang dianjurkan. Meskipun demikian, mutex tetap bisa digunakan dengan cara tanpa ditempelkan ke objek yang memerlukan lock-unlock. Contohnya bisa dilihat dibawah ini. + +```go +func (c *counter) Add(x int) { + c.val++ +} + +func (c *counter) Value() (x int) { + return c.val +} + +func main() { + runtime.GOMAXPROCS(2) + + var wg sync.WaitGroup + var mtx sync.Mutex + var meter counter + + for i := 0; i < 1000; i++ { + wg.Add(1) + + go func() { + for j := 0; j < 1000; j++ { + mtx.Lock() + meter.Add(1) + mtx.Unlock() + } + + wg.Done() + }() + } + + wg.Wait() + fmt.Println(meter.Value()) +} +``` + +--- + + diff --git a/6-hello-world.md b/6-hello-world.md new file mode 100644 index 000000000..578ad4943 --- /dev/null +++ b/6-hello-world.md @@ -0,0 +1,151 @@ +# A.6. Program Pertama: Hello World + +Semua persiapan sudah selesai, saatnya mulai masuk pada sesi pembuatan program. Program pertama yang akan kita buat adalah aplikasi kecil yang menampilkan tulisan **Hello World**. + +Di bab ini akan dijelaskan secara komprehensif step-by-step mulai dari awal. Mulai pembuatan project, pembuatan file program, sesi penulisan kode (coding), hingga eksekusi aplikasi. + +## A.6.1. Load `GOPATH` Ke Editor + +> Jika kawan-kawan menggunakan Go Modules, maka silakan langsung loncat ke step [A.6.3](#a63-menyiapkan-file-program), langsung drag saja folder projek ke editor atau IDE yang digunakan. + +OK, hal pertama yang perlu dilakukan, adalah me-load atau memunculkan folder `GOPATH` di editor. Dengan begitu proyek-proyek Go akan lebih mudah di-maintain. Caranya: + + 1. Buka editor yang digunakan. + 2. Cari menu untuk menambahkan projek, lalu pilih folder `GOPATH`. Untuk beberapa jenis editor bisa cukup dengan klik-drag folder tersebut ke editor. + +![GOPATH di editor](images/A.6_1_editor_project_explorer.png) + +> Nama variabel di sistem operasi non-Windows diawali dengan tanda dollar `$`, sebagai contoh `$GOPATH`. Sedangkan di Windows, nama variabel diapit karakter persen `%`, contohnya seperti `%GOPATH%`. + +## A.6.2. Menyiapkan Folder Project + +Selanjutnya, buat project folder baru dalam `$GOPATH/src`, dengan nama folder bebas (boleh menggunakan nama `belajar-golang` atau lainnya). Agar lebih praktis, buat folder tersebut lewat editor yang digunakan. Berikut adalah caranya. + + 1. Klik kanan di folder `src`. + 2. Klik **New Folder**, di bagian bawah akan muncul inputan kecil **Folder Name**. + 3. Ketikkan nama folder, **belajar-golang**, lalu enter. + +![Buat proyek di editor](images/A.6_2_new_project_on_editor.png) + +## A.6.3. Menyiapkan File Program + +File program disini maksudnya adalah file yang berisikan kode program Go, berekstensi `.go`. + +Di dalam project yang telah dibuat (`$GOPATH/src/belajar-golang/`), siapkan sebuah file dengan nama bebas, yang jelas harus ber-ekstensi `.go`. Pada contoh ini saya menggunakan nama file `bab6-hello-world.go`. + +Pembuatan file program juga akan dilakukan lewat editor. Caranya silakan ikut petunjuk berikut. + + 1. Klik kanan di folder `belajar-golang`. + 2. Klik **New File**, di bagian bawah akan muncul inputan kecil **File Name**. + 3. Ketikkan nama file, **belajar-golang**, lalu enter. + +![Buat file di editor](images/A.6_3_new_file_on_editor.png) + +## A.6.4. Program Pertama: Hello Word + +Setelah project folder dan file program sudah siap, saatnya untuk **coding**. + +Dibawah ini merupakan contoh kode program sederhana untuk memunculkan text atau tulisan **"hello world"** ke layar output (command line). Silakan salin kode berikut ke file program yang telah dibuat. Sebisa mungkin jangan copy paste. Biasakan untuk menulis dari awal, agar cepat terbiasa dan familiar dengan Go. + +```go +package main + +import "fmt" + +func main() { + fmt.Println("hello world") +} +``` + +Setelah kode disalin, buka terminal (atau CMD bagi pengguna Windows), lalu masuk ke direktori proyek menggunakan perintah `cd`. + + - Windows + + ```bash + $ cd %GOPATH%\src\belajar-golang + ``` + + - Non-Windows + + ```bash + $ cd $GOPATH/src/belajar-golang + ``` + + +Jalankan program dengan perintah `go run`. + +```bash +$ go run bab6-hello-world.go +``` + +Hasilnya, muncul tulisan **hello world** di layar console. + +![Menjalankan program](images/A.6_4_execute_hello_world.png) + +Selamat! Anda telah berhasil membuat program menggunakan Go! + +--- + +Meski kode program di atas sangat sederhana, mungkin akan muncul beberapa pertanyaan di benak. Di bawah ini merupakan detail penjelasan kode di atas. + +## A.6.5. Penggunaan Keyword `package` + +Setiap file program harus memiliki package. Setiap project harus ada minimal satu file dengan nama package `main`. File yang ber-package `main`, akan di eksekusi pertama kali ketika program di jalankan. + +Cara menentikan package dengan menggunakan keyword `package`, berikut adalah contoh penulisannya. + +```go +package +package main +``` + +## A.6.6. Penggunaan Keyword `import` + +Keyword `import` digunakan untuk meng-include atau memasukan package lain kedalam file program, agar isi package yang di-include bisa dimanfaatkan. + +Package `fmt` merupakan salah satu package yang disediakan oleh Go, berisikan banyak fungsi untuk keperluan **I/O** yang berhubungan dengan text. + +Skema penulisan keyword `import` bisa dilihat pada contoh berikut. + +```go +import "" +import "fmt" +``` + +## A.6.7. Penggunaan Fungsi `main()` + +Dalam sebuah proyek harus ada file program yang berisikan sebuah fungsi bernama `main()`. Fungsi tersebut harus berada dalam package yang juga bernama `main`. Fungsi `main()` adalah yang dipanggil pertama kali pada saat eksekusi program. Contoh penulisan fungsi `main`: + +```go +func main() { + +} +``` + +## A.6.8. Penggunaan Fungsi `fmt.Println()` + +Fungsi `fmt.Println()` digunakan untuk memunculkan text ke layar (pada konteks ini, terminal atau CMD). Di program pertama yang telah kita buat, fungsi ini memunculkan tulisan **Hello World**. + +Skema penulisan keyword `fmt.Println()` bisa dilihat pada contoh berikut. + +```go +fmt.Println("") +fmt.Println("hello world") +``` + +Fungsi `fmt.Println()` berada dalam package `fmt`, maka untuk menggunakannya perlu package tersebut untuk di-import terlebih dahulu. + +Fungsi `fmt.Println()` dapat menampung parameter yang tidak terbatas jumlahnya. Semua data parameter akan dimunculkan dengan pemisah tanda spasi. + +```go +fmt.Println("hello", "world!", "how", "are", "you") +``` + +Outputnya: **hello world! how are you**. + +--- + + diff --git a/7-komentar.md b/7-komentar.md new file mode 100644 index 000000000..8b8a8642b --- /dev/null +++ b/7-komentar.md @@ -0,0 +1,52 @@ +# A.7. Komentar + +Komentar biasa dimanfaatkan untuk menyisipkan catatan pada kode program, menulis penjelasan/deskripsi mengenai suatu blok kode, atau bisa juga digunakan untuk me-remark kode (men-non-aktifkan kode yg tidak digunakan). Komentar akan diabaikan ketika kompilasi maupun eksekusi program. + +Ada 2 jenis komentar di Go, inline & multiline. Di bab akan dijelaskan tentang penerapan dan perbedaan kedua jenis komentar tersebut. + +## A.7.1. Komentar Inline + +Penulisan komentar jenis ini di awali dengan tanda **double slash** (`//`) lalu diikuti pesan komentarnya. Komentar inline hanya berlaku utuk satu baris pesan saja. Jika pesan komentar lebih dari satu baris, maka tanda `//` harus ditulis lagi di baris selanjutnya. + +```go +package main + +import "fmt" + +func main() { + // komentar kode + // menampilkan pesan hello world + fmt.Println("hello world") + + // fmt.Println("baris ini tidak akan di eksekusi") +} +``` + +Mari kita praktekan kode di atas. Siapkan file program baru dalam project folder `belajar-golang` dengan nama bebas. Isi dengan kode di atas, lalu jalankan. + +![Contoh komentar inline](images/A.7_1_inline_comment.png) + +Hasilnya hanya tulisan **hello world** saja yang muncul di layar, karena semua yang di awali tanda double slash `//` diabaikan oleh compiler. + +## A.7.2. Komentar Multiline + +Komentar yang cukup panjang akan lebih rapi jika ditulis menggunakan teknik komentar multiline. Ciri dari komentar jenis ini adalah penulisannya diawali dengan tanda `/*` dan diakhiri `*/`. + +```go +/* + komentar kode + menampilkan pesan hello world +*/ +fmt.Println("hello world") + +// fmt.Println("baris ini tidak akan di eksekusi") +``` + +Sifat komentar ini sama seperti komentar inline, yaitu sama-sama diabaikan oleh compiler. + +--- + + diff --git a/8-variabel.md b/8-variabel.md new file mode 100644 index 000000000..fb5464276 --- /dev/null +++ b/8-variabel.md @@ -0,0 +1,188 @@ +# A.8. Variabel + +Go mengadopsi dua jenis penulisan variabel, yang dituliskan tipe data-nya dan yang tidak. Kedua cara tersebut valid dan tujuannya sama, pembedanya hanya cara penulisannya saja. + +Pada bab ini akan dikupas tuntas tentang macam-macam cara deklarasi variabel. + +## A.8.1. Deklarasi Variabel Beserta Tipe Data + +Go memiliki aturan cukup ketat dalam hal penulisan variabel. Ketika deklarasi, tipe data yg digunakan harus dituliskan juga. Istilah lain dari konsep ini adalah **manifest typing**. + +Berikut adalah contoh cara pembuatan variabel yang tipe datanya harus ditulis. + +```go +package main + +import "fmt" + +func main() { + var firstName string = "john" + + var lastName string + lastName = "wick" + + fmt.Printf("halo %s %s!\n", firstName, lastName) +} +``` + +Keyword `var` di atas digunakan untuk deklarasi variabel, contohnya bisa dilihat pada `firstName` dan `lastName`. + +Nilai variabel `firstName` diisi langsung ketika deklarasi, berbeda dibanding `lastName` yang nilainya diisi setelah baris kode deklarasi, hal seperti ini diperbolehkan di Go. + +![Menampilkan isi variabel](images/A.8_1_variabel.png) + +## A.8.2. Deklarasi Variabel Menggunakan Keyword `var` + +Pada kode di atas bisa dilihat bagaimana sebuah variabel dideklarasikan dan di-set nilainya. Keyword `var` digunakan untuk membuat variabel baru. + +Skema penggunaan keyword var: + +```go +var +var = +``` + +Contoh: + +```go +var lastName string +var firstName string = "john" +``` + +Nilai variabel bisa di-isi langsung pada saat deklarasi variabel. + +### A.8.2.1. Penggunaan Fungsi `fmt.Printf()` + +Fungsi ini digunakan untuk menampilkan output dalam bentuk tertentu. Kegunaannya sama seperti fungsi `fmt.Println()`, hanya saja struktur outputnya didefinisikan di awal. + +Perhatikan bagian `"halo %s %s!\n"`, karakter `%s` disitu akan diganti dengan data `string` yang berada di parameter ke-2, ke-3, dan seterusnya. + +Ketiga baris kode di bawah ini menghasilkan output yang sama, meskipun cara penulisannya berbeda. + +```go +fmt.Printf("halo john wick!\n") +fmt.Printf("halo %s %s!\n", firstName, lastName) +fmt.Println("halo", firstName, lastName + "!") +``` + +Tanda plus (`+`) jika ditempatkan di antara string, fungsinya adalah untuk penggabungan string (istilah lainnya: concatenation). + +Fungsi `fmt.Printf()` tidak menghasilkan baris baru di akhir text, oleh karena itu digunakanlah literal `\n` untuk memunculkan baris baru di akhir. Hal ini sangat berbeda jika dibandingkan dengan fungsi `fmt.Println()` yang secara otomatis menghasilkan new line (baris baru) di akhir. + +## A.8.3. Deklarasi Variabel Tanpa Tipe Data + +Selain **manifest typing**, Go juga mengadopsi metode **type inference**, yaitu metode deklarasi variabel yang tipe data-nya ditentukan oleh tipe data nilainya, cara kontradiktif jika dibandingkan dengan cara pertama. Dengan metode jenis ini, keyword `var` dan tipe data tidak perlu ditulis. + +```go +var firstName string = "john" +lastName := "wick" + +fmt.Printf("halo %s %s!\n", firstName, lastName) +``` + +Variabel `lastName` dideklarasikan dengan menggunakan metode type inference. Penandanya tipe data tidak dituliskan pada saat deklarasi. Pada penggunaan metode ini, operand `=` harus diganti dengan `:=` dan keyword `var` dihilangkan. + +Tipe data `lastName` secara otomatis akan ditentukan menyesuaikan value atau nilai-nya. Jika nilainya adalah berupa `string` maka tipe data variabel adalah `string`. Pada contoh di atas, nilainya adalah string `"wick"`. + +Diperbolehkan untuk tetap menggunakan keyword `var` pada saat deklarasi meskipun tanpa menuliskan tipe data, dengan ketentuan tidak menggunakan tanda `:=`, melainkan tetap menggunakan `=`. + +```go +// menggunakan var, tanpa tipe data, menggunakan perantara "=" +var firstName = "john" + +// tanpa var, tanpa tipe data, menggunakan perantara ":=" +lastName := "wick" +``` + +Kedua deklarasi di atas maksudnya sama. Silakan pilih yang nyaman di hati. + +Tanda `:=` hanya digunakan sekali di awal pada saat deklarasi. Untuk assignment nilai selanjutnya harus menggunakan tanda `=`, contoh: + +```go +lastName := "wick" +lastName = "ethan" +lastName = "bourne" +``` + +> Perlu diketahu, deklarasi menggunakan `:=` hanya bisa dilakukan di dalam blok fungsi. + +## A.8.4. Deklarasi Multi Variabel + +Go mendukung metode deklarasi banyak variabel secara bersamaan, caranya dengan menuliskan variabel-variabel-nya dengan pembatas tanda koma (`,`). Untuk pengisian nilainya-pun diperbolehkan secara bersamaan. + +```go +var first, second, third string +first, second, third = "satu", "dua", "tiga" +``` + +Pengisian nilai juga bisa dilakukan bersamaan pada saat deklarasi. Caranya dengan menuliskan nilai masing-masing variabel berurutan sesuai variabelnya dengan pembatas koma (`,`). + +```go +var fourth, fifth, sixth string = "empat", "lima", "enam" +``` + +Kalau ingin lebih ringkas: + +```go +seventh, eight, ninth := "tujuh", "delapan", "sembilan" +``` + +Dengan menggunakan teknik type inference, deklarasi multi variabel bisa dilakukan untuk variabel-variabel yang tipe data satu sama lainnya berbeda. + +``` +one, isFriday, twoPointTwo, say := 1, true, 2.2, "hello" +``` + +## A.8.5. Variabel Underscore `_` + +Go memiliki aturan unik yang jarang dimiliki bahasa lain, yaitu tidak boleh ada satupun variabel yang menganggur. Artinya, semua variabel yang dideklarasikan harus digunakan. Jika ada variabel yang tidak digunakan tapi dideklarasikan, error akan muncul dan program tidak bisa di-run ataupun di-compile. + +![Variabel pengangguran](images/A.8_2_unused_variabel.png) + +Underscore (`_`) adalah predefined variabel yang bisa dimanfaatkan untuk menampung nilai yang tidak dipakai. Bisa dibilang variabel ini merupakan keranjang sampah. + +```go +_ = "belajar Golang" +_ = "Golang itu mudah" +name, _ := "john", "wick" +``` + +Pada contoh di atas, variabel `name` akan berisikan text `john`, sedang nilai `wick` ditampung oleh variabel underscore, menandakan bahwa nilai tersebut tidak akan digunakan. + +Variabel underscore adalah predefined, jadi tidak perlu menggunakan `:=` untuk pengisian nilai, cukup dengan `=` saja. Namun khusus untuk pengisian nilai multi variabel yang dilakukan dengan metode type inference, boleh didalamnya terdapat variabel underscore. + +Biasanya variabel underscore sering dimanfaatkan untuk menampung nilai balik fungsi yang tidak digunakan. + +Perlu diketahui, bahwa isi variabel underscore tidak dapat ditampilkan. Data yang sudah masuk variabel tersebut akan hilang. Ibaratkan variabel underscore seperti blackhole, objek apapun yang masuk kedalamnya, akan terjebak selamanya di-dalam singularity dan tidak akan bisa keluar 😁 + +## A.8.6. Deklarasi Variabel Menggunakan Keyword `new` + +Keyword `new` digunakan untuk membuat variabel **pointer** dengan tipe data tertentu. Nilai data default-nya akan menyesuaikan tipe datanya. + +```go +name := new(string) + +fmt.Println(name) // 0x20818a220 +fmt.Println(*name) // "" +``` + +Variabel `name` menampung data bertipe **pointer string**. Jika ditampilkan yang muncul bukanlah nilainya melainkan alamat memori nilai tersebut (dalam bentuk notasi heksadesimal). Untuk menampilkan nilai aslinya, variabel tersebut perlu di-**dereference** terlebih dahulu, menggunakan tanda asterisk (`*`). + +Mungkin untuk sekarang banyak yang akan bingung tentang apa itu pointer, namun tak apa, karena nantinya di [Bab A.22. Pointer](/22-pointer.html) akan dikupas habis topik tersebut. + +## A.8.7. Deklarasi Variabel Menggunakan Keyword `make` + +Keyword ini hanya bisa digunakan untuk pembuatan beberapa jenis variabel saja, yaitu: + +- channel +- slice +- map + +Dan lagi, mungkin banyak yang akan bingung. Ketika sudah masuk ke pembahasan masing-masing poin tersebut, akan terlihat apa kegunaan dari keyword `make` ini. + +--- + + diff --git a/9-tipe-data.md b/9-tipe-data.md new file mode 100644 index 000000000..60d4a6ab2 --- /dev/null +++ b/9-tipe-data.md @@ -0,0 +1,125 @@ +# A.9. Tipe Data + +Go mengenal beberapa jenis tipe data, diantaranya adalah tipe data numerik (desimal & non-desimal), string, dan boolean. + +Di bab-bab sebelumnya secara tak sadar kita sudah mengaplikasikan beberapa tipe data, seperti `string` dan tipe numerik `int`. + +Pada bab ini akan dijelaskan beberapa macam tipe data standar yang disediakan oleh Go, beserta cara penggunaannya. + +## A.9.1. Tipe Data Numerik Non-Desimal + +Tipe data numerik non-desimal atau **non floating point** di Go ada beberapa jenis. Secara umum ada 2 tipe data kategori ini yang perlu diketahui. + + - `uint`, tipe data untuk bilangan cacah (bilangan positif). + - `int`, tipe data untuk bilangan bulat (bilangan negatif dan positif). + +Kedua tipe data di atas kemudian dibagi lagi menjadi beberapa jenis, dengan pembagian berdasarkan lebar cakupan nilainya, detailnya bisa dilihat di tabel berikut. + +| Tipe data | Cakupan bilangan | +| :-------: | :---- | +| `uint8` | 0 ↔ 255 | +| `uint16` | 0 ↔ 65535 | +| `uint32` | 0 ↔ 4294967295 | +| `uint64` | 0 ↔ 18446744073709551615 | +| `uint` | sama dengan `uint32` atau `uint64` (tergantung nilai) | +| `byte` | sama dengan `uint8` | +| `int8` | -128 ↔ 127 | +| `int16` | -32768 ↔ 32767 | +| `int32` | -2147483648 ↔ 2147483647 | +| `int64` | -9223372036854775808 ↔ 9223372036854775807 | +| `int` | sama dengan `int32` atau `int64` (tergantung nilai) | +| `rune` | sama dengan `int32` | + +Dianjurkan untuk tidak sembarangan dalam menentukan tipe data variabel, sebisa mungkin tipe yang dipilih harus disesuaikan dengan nilainya, karena efeknya adalah ke alokasi memori variabel. Pemilihan tipe data yang tepat akan membuat pemakaian memori lebih optimal, tidak berlebihan. + +```go +var positiveNumber uint8 = 89 +var negativeNumber = -1243423644 + +fmt.Printf("bilangan positif: %d\n", positiveNumber) +fmt.Printf("bilangan negatif: %d\n", negativeNumber) +``` + +Variabel `positiveNumber` bertipe `uint8` dengan nilai awal `89`. Sedangkan variabel `negativeNumber` dideklarasikan dengan nilai awal `-1243423644`. Compiler secara cerdas akan menentukan tipe data variabel tersebut sebagai `int32` (karena angka tersebut masuk ke cakupan tipe data `int32`). + +Template `%d` pada `fmt.Printf()` digunakan untuk memformat data numerik non-desimal. + +## A.9.2. Tipe Data Numerik Desimal + +Tipe data numerik desimal yang perlu diketahui ada 2, `float32` dan `float64`. Perbedaan kedua tipe data tersebut berada di lebar cakupan nilai desimal yang bisa ditampung. Untuk lebih jelasnya bisa merujuk ke spesifikasi [IEEE-754 32-bit floating-point numbers](http://www.h-schmidt.net/FloatConverter/IEEE754.html). + +```go +var decimalNumber = 2.62 + +fmt.Printf("bilangan desimal: %f\n", decimalNumber) +fmt.Printf("bilangan desimal: %.3f\n", decimalNumber) +``` + +Pada kode di atas, variabel `decimalNumber` akan memiliki tipe data `float32`, karena nilainya berada di cakupan tipe data tersebut. + +![Tipe data numerik desimal](images/A.9_1_decimal_data_type.png) + +Template `%f` digunakan untuk memformat data numerik desimal menjadi string. Digit desimal yang akan dihasilkan adalah **6 digit**. Pada contoh di atas, hasil format variabel `decimalNumber` adalah `2.620000`. Jumlah digit yang muncul bisa dikontrol menggunakan `%.nf`, tinggal ganti `n` dengan angka yang diinginkan. Contoh: `%.3f` maka akan menghasilkan 3 digit desimal, `%.10f` maka akan menghasilkan 10 digit desimal. + +## A.9.3. Tipe Data `bool` (Boolean) + +Tipe data `bool` berisikan hanya 2 variansi nilai, `true` dan `false`. Tipe data ini biasa dimanfaatkan dalam seleksi kondisi dan perulangan (yang nantinya akan kita bahas pada bab 12 dan bab 13). + +```go +var exist bool = true +fmt.Printf("exist? %t \n", exist) +``` + +Gunakan `%t` untuk memformat data `bool` menggunakan fungsi `fmt.Printf()`. + +## A.9.4. Tipe Data `string` + +Ciri khas dari tipe data string adalah nilainya di apit oleh tanda *quote* atau petik dua (`"`). Contoh penerapannya: + +```go +var message string = "Halo" +fmt.Printf("message: %s \n", message) +``` + +Selain menggunakan tanda quote, deklarasi string juga bisa dengan tanda *grave accent/backticks* (`), tanda ini terletak di sebelah kiri tombol 1. Keistimewaan string yang dideklarasikan menggunakan backtics adalah membuat semua karakter didalamnya **tidak di escape**, termasuk `\n`, tanda petik dua dan tanda petik satu, baris baru, dan lainnya. Semua akan terdeteksi sebagai string. + +```go +var message = `Nama saya "John Wick". +Salam kenal. +Mari belajar "Golang".` + +fmt.Println(message) +``` + +Ketika dijalankan, output akan muncul sama persisi sesuai nilai variabel `message` di atas. Tanda petik dua akan muncul, baris baru juga muncul, sama persis. + +![String menggunakan grave accent](images/A.9_2_unescaped_string.png) + +## A.9.5. Nilai `nil` & Zero Value + +`nil` bukan merupakan tipe data, melainkan sebuah nilai. Variabel yang isi nilainya `nil` berarti memiliki nilai kosong. + +Semua tipe data yang sudah dibahas di atas memiliki zero value (nilai default tipe data). Artinya meskipun variabel dideklarasikan dengan tanpa nilai awal, tetap akan ada nilai default-nya. + + - Zero value dari `string` adalah `""` (string kosong). + - Zero value dari `bool` adalah `false`. + - Zero value dari tipe numerik non-desimal adalah `0`. + - Zero value dari tipe numerik desimal adalah `0.0`. + +Zero value berbeda dengan `nil`. `Nil` adalah nilai kosong, benar-benar kosong. `nil` tidak bisa digunakan pada tipe data yang sudah dibahas di atas. Ada beberapa tipe data yang bisa di-set nilainya dengan `nil`, diantaranya: + +- pointer +- tipe data fungsi +- slice +- `map` +- `channel` +- interface kosong atau `interface{}` + +Nantinya kita akan sering bertemu dengan `nil` setelah masuk pada pembahasan bab-bab tersebut. + +--- + + diff --git a/A-58-go-vendoring.md b/A-58-go-vendoring.md new file mode 100644 index 000000000..3be78dc19 --- /dev/null +++ b/A-58-go-vendoring.md @@ -0,0 +1,82 @@ +# A.58. Go Vendoring + +Pada bagian ini kita akan belajar cara pemanfaatan vendoring untuk mempermudah manajemen package atau dependency dalam project Go. + +Pada [Bab A.3. GOPATH Dan Workspace](/3-gopath-dan-workspace.html) sudah disinggung bahwa project Go harus ditempatkan didalam workspace, lebih spesifiknya dalam folder `$GOPATH/src/`. Aturan ini cukup memicu perdebatan di komunitas, karena menghasilkan efek negatif terhadap beberapa hal, yang salah satunya adalah: dependency management yang dirasa susah. + +## A.58.1. Perbandingan Project Yang Menerapkan Vendoring vs Tidak + +Penulis coba contohkan dengan sebuah kasus untuk mempermudah pembaca memahami permasalahan yang ada dalam dependency management di Go. + +Dimisalkan, ada dua buah projek yang sedang di-develop, `project-one` dan `project-two`. Keduanya depend terhadap salah satu 3rd party library yg sama, [gubrak](https://github.com/novalagung/gubrak). Di dalam `project-one`, versi gubrak yang digunakan adalah `v0.9.1-alpha`, sedangkan di `project-two` versi `v1.0.0` digunakan. Pada `project-one` versi yang digunakan cukup tua karena proses pengembangannya sudah agak lama, dan aplikasinya sendiri sudah stabil, jika di upgrade paksa ke gubrak versi `v1.0.0` kemungkinan besar terjadi error dan panic. + +Kedua projek tersebut pastinya akan lookup gubrak ke direktori yang sama, yaitu `$GOPATH/src/github.com/novalagung/gubrak`. Efeknya, ketika sedang bekerja pada `project-one`, harus dipastikan current revision pada repository gubrak di lokal adalah sesuai dengan versi `v1.0.0`. Dan, ketika mengerjakan `project-two` maka current revision gubrak harus sesuai dengan versi `v0.9.1-alpha`. Repot sekali bukan? + +Setelah beberapa waktu, akhirnya `go1.6` rilis, dengan membawa kabar baik, yaitu rilisnya fasilitas baru **vendoring**. Vendoring ini berguna untuk men-centralize packages atau dependencies atau 3rd party libraries yang digunakan dalam spesifik project. + +Penggunaannya sendiri sangat mudah, cukup tempatkan 3rd party library ke-dalam folder `vendor`, yang berada di dalam masing-masing project. By default go akan memprioritaskan lookup pada folder `vendor`. + +> Folder vendor ini kegunaannya sama seperti folder `node_modules` dalam project javascript. + +Kita kembali ke contoh, library gubrak dipindahkan ke folder `vendor` dalam `project-one`, maka struktur project tersebut plus vendoring menjadi seperti berikut. + +```bash +$GOPATH/src/project-one +$GOPATH/src/project-one/vendor/github.com/novalagung/gubrak +``` + +Source code library gubrak dalam folder `vendor` harus ditempatkan sesuai dengan struktur-nya. + +```bash +$ tree . +. +├── main.go +└── vendor + └── github.com + └── novalagung + └── gubrak + ├── date.go + ├── is.go + └── ... + +4 directories, N files +``` + +Isi `project-one/main.go` sendiri cukup sederhana, sebuah program kecil yang menampilkan angka random dengan range 10-20. + +```go +package main + +import ( + "fmt" + "github.com/novalagung/gubrak" +) + +func main() { + fmt.Println(gubrak.RandomInt(10, 20)) +} +``` + +Ketika di build, dengan flag `-v` terlihat perbedaan antara projek yang menerapkan vendoring maupun yang tidak. + +- Untuk yang tidak menggunakan vendor folder, maka akan lookup ke folder library yang digunakan, di `$GOPATH`. +- Untuk project yang menerapkan vendoring, maka tetap lookup juga, tetapi dalam sub folder `vendor` yang ada di dalam projek tersebut. + +![Using vendor vs not using vendor](images/A.58_1_vendor_vs_nope.png) + +## A.58.2. Manajemen Dependencies Dalam Folder `vendor` + +Cara menambahkan dependency ke folder `vendor` bisa dengan cara copy-paste, yang tetapi jelasnya adalah tidak praktis (dan bisa menimbulkan masalah). Cara yang lebih benar adalah dengan menggunakan package management tools. + +Di go, ada sangat banyak sekali package management tools. Sangat. Banyak. Sekali. Silakan cek di [PackageManagementTools](https://github.com/golang/go/wiki/PackageManagementTools) untuk lebih detailnya. + +Pembaca bebas memilih untuk menggunakan package management tools yang mana. Namun di buku ini, **Dep** akan kita bahas. Dep sendiri merupakan official package management tools untuk Go dari Go Team. + +Pembahasan mengenai Dep ada di bab selanjutnya. + +--- + + diff --git a/A-59-go-dep.md b/A-59-go-dep.md new file mode 100644 index 000000000..518649b8f --- /dev/null +++ b/A-59-go-dep.md @@ -0,0 +1,68 @@ +# A.58. Dep - Go Dependency Management Tool + +Dep adalah Go Official Package Management Tools, diperkenalkan pada go versi `go1.9`. Pada bab ini kita akan belajar penggunaannya. + +Dengan menggunakan Dep, maka semua 3rd party libraries yang dipakai dalam suatu project akan dicatat dan ditempatkan di dalam sub folder `vendor` pada project tersebut. + +> Jika kawan-kawan menggunakan `$GOPATH`, silakan memanfaatkan Dep ini untuk manajemen dependensi. Tetapi jika menggunakan [Go Modules](/A-60-go-modules.html) penulis sarankan untuk menggunakan fasilitas package manager yang sudah ter-bundle dalam Go Modules. + +## A.58.1. Instalasi Dep + +Silakan ikuti petunjuk di [Dep Installation](https://golang.github.io/dep/docs/installation.html), ada beberapa cara yang bisa dipilih untuk meng-install Dep. + +## A.58.2. Inisialisasi Dep Pada Project + +Buat project baru, isi dengan kode sederhana untuk menampilkan angka random (sama seperti pada bab sebelumnya). Library yang kita gunakan adalah [gubrak](https://github.com/novalagung/gubrak). + +```go +package main + +import ( + "fmt" + "github.com/novalagung/gubrak" +) + +func main() { + fmt.Println(gubrak.RandomInt(10, 20)) +} +``` + +Jalankan command `dep init` untuk meng-inisialisasi project agar ditandai sebagai project yang menggunakan package manager Dep. Command tersebut menghasilkan 3 buah file/folder baru. + +- File `Gopkg.toml`, berisikan metada semua dependencies yang digunakan dalam project. +- File `Gopkg.lock`, isinya mirip seperti `Gopkg.toml` hanya saja lebih mendetail informasi tiap-tiap dependency yang disimpan dalam file ini. +- Folder `vendor`, akan berisi source code dari pada 3rd party yang digunakan. + +Eksekusi command `dep init` akan secara otomatis diikuti dengan eksekusi `dep ensure`. Command `dep ensure` sendiri merupakan command paling penting dalam Dep, gunanya untuk sinkronisasi dan memastikan bahwa semua 3rd party libraries digunakan dalam project metadata-nya ter-mapping dengan baik dan benar dalam `Gopkg.*`, dan source code 3rd party sendiri ada dalam `vendor`. + +```bash +$ tree -L 1 . +. +├── Gopkg.lock +├── Gopkg.toml +├── main.go +└── vendor + +1 directory, 3 files +``` + +Selesai. Sesederhana ini. Cukup mudah bukan? + +## A.58.3. Add & Update Dependency + +Gunakan `dep ensure -add ` untuk menambahkan library. Gunakan `dep ensure -update ` untuk meng-update library yang sudah tercatat. + +Penggunaan dua command tersebut jelasnya akan mengubah informasi dalam file `Gopkg.*`. Pastikan bahwa library yang didaftarkan adalah yang memang digunakan di project. Jika ragu, maka hapus saja folder `vendor`, lalu eksekusi command `dep ensure` untuk sinkronisasi ulang. + +Contoh command update: + +```bash +$ dep ensure -update github.com/novalagung/gubrak +``` + +--- + + diff --git a/A-60-go-modules.md b/A-60-go-modules.md new file mode 100644 index 000000000..27072e473 --- /dev/null +++ b/A-60-go-modules.md @@ -0,0 +1,78 @@ +# A.60. Go Modules + +Pada bagian ini kita akan belajar cara pemanfaatan Go Modules untuk manajemen project dan dependency. Go Modules merupakan bagian dari **Go Toolchain** yang sekarang sedang dikembangkan. + +Sebelum kita belajar mengenai go modules secara intensif, ada baiknya kita coba melihat kebelakang sejenak untuk mengenang `$GOPATH`. + +## A.60.1. *The Infamous* `$GOPATH` + +Sebelumnya sudah di bahas dalam [Bab A.3. GOPATH Dan Workspace](/3-gopath-dan-workspace.html) bahwa project Go harus ditempatkan didalam workspace, lebih spesifiknya dalam folder `$GOPATH/src/`. Sudah dibahas juga bahwa ada baiknya untuk men-centralize 3rd party library yang digunakan per project dengan memanfaatkan [vendoring](/A-58-go-vendoring.html) dan [package management tools: Dep](/A-59-go-dep.html). + +Sebenarnya masih ada satu masalah yang masih belum terselesaikan yang berhubungan dengan manajemen project dan dependency di Go. Yaitu `$GOPATH`. Ya, `$GOPATH` adalah **masalah besar** untuk beberapa kasus. + +Perlu diketahui, meskipun sebuah project sudah menerapkan vendoring (semua 3rd party sudah ada di sub folder `vendor`), project itu sendiri masih harus ditempatkan dalam workspace! + +Silakan lihat gambar di bawah ini untuk pembuktian. Di dalam project `chapter-A.60-go-modules` terdapat package `models` yang harus di-import. Karena project tidak ditempatkan dalam workspace, eksekusinya gagal. + +![Import error](images/A.60_1_import_error.png) + +> Jika dalam satu project hanya ada satu package, `main` saja, maka meski projek tidak ditempatkan dalam workspace, tidak akan terjadi error. Tapi jelasnya tidak mungkin dalam real world development sebuah project hanya memilik satu package saja. Brutal sekali kalau misal ada 😁 + +Bisa dilihat dari stack trace error pada gambar di atas, proses pencarian package `models` dilakukan hanya pada `$GOROOT` dan `$GOPATH` saja, pencarian dalam folder project itu sendiri tidak dilakukan. Ini merupakan pengingat halus bahwa project ini harus ditempatkan dalam workspace, agar package `models`-nya bisa dikenali. + +Masalah bukan? + +## A.60.2. Go Modules + +Setelah cukup lama menunggu, akhirnya pada rilisnya `go1.11` dikenalkanlah sebuah fasilitas baru bernama **Go Modules** (experimental). Tujuan diciptakannya Go Modules ini adalah untuk mengatasi masalah ketergantuan sebuah project dengan `$GOPATH`. Jadi dengan mengimplementasikan Go Modules, sebuah project tidak harus berada dalam `$GOPATH`. + +Cara penerapan Go Modules cukup mudah. Inisialisasi project sebagai module lewat command `go mod init `. + +![Go Mod](images/A.60_2_go_mod.png) + +Bisa dilihat di gambar, bahwa setelah project di-inisialisasi sebagai module, proses running aplikasi menjadi sukses. + +## A.60.3. Penjelasan `go mod` + +Command `go mod init` menghasilkan sebuah file bernama `go.mod`, yang isinya adalah konfigurasi module. + +Ok, mungkin akan muncul beberapa pertanyaan. Salah satunya adalah kenapa command-nya harus `go mod init chapter-A.60-go-modules`, kenapa tidak cukup hanya `go mod init` saja? Hal ini karena mulai awal, projek ini sudah tidak didalam `$GOPATH`. Argument `chapter-A.60-go-modules` setelah command inisialisasi berguna untuk menentukan package path dari project, hal ini penting untuk keperluan import package lain yang merupakan sub folder project. + +Agar lebih mudah untuk dipahami, silakan lihat contoh berikut. + +![Example Module Name 1](images/A.60_3_module_name_1.png) + +Coba perhatikan gambar di atas. Nama folder adalah `chapter-A.60-go-modules`, tetapi pada saat import `models`, package path yang digunakan adalah `myproject/models`. Harusnya ini error kan? Tetapi tidak, *hmmmm*. + +Dengan menggunakan Go Modules kita bisa menentukan base package path suatu project, tidak harus sama dengan nama folder-nya. Pada contoh ini nama folder adalah `chapter-A.60-go-modules` tapi base package path adalah `myproject`. + +Agar lebih jelas lagi, lanjut ke contoh ke-dua. + +![Example Module Name 2](images/A.60_4_module_name_2.png) + +Pada contoh ini, nama module di-set panjang, `github.com/novalagung/myproject`. Dengan nama folder masih tetap sama. Hasilnya aplikasi tetap berjalan lancar. + +Nama package path bisa di set dalam bentuk arbitrary path seperti pada gambar. Sangat membantu bukan sekali, tidak perlu membuat nested folder terlebih dahulu seperti yang biasa dilakukan dalam `$GOPATH`. + +## A.60.4. Vendoring Dengan Go Modules + +Vendoring, seperti yang kita tau berguna untuk men-centralize 3rd party libraries. Sedangkan dari yang sudah dipelajari, kita bisa membuat sebuah project untuk tidak tergantung dengan `$GOPATH` lewat Go Modules. + +Sebenarnya, Go Modules, selain untuk membuat sebuah project tidak tergantung dengan `$GOPATH`, adalah juga berguna untuk manajemen dependencies project. Jadi dengan Go Modules tidak perlu menggunakan `dep`, karena di dalam Go Modules sudah include kapabilitas yang sama dengan `dep` yaitu untuk me-manage dependencies. + +Cara untuk meng-enable vendoring dengan Go Modules, adalah lewat command `go mod vendor`. + +![Go Modules + Vendoring](images/A.60_5_mod_vendor.png) + +Jika pada sebuah projek sudah enabled `dep`, dan ingin di enable Go Modules. Maka file `Gopkg.lock` yang sudah ada akan dikonversi ke-dalam bentuk `go.mod` dan `go.sum`. + +## A.60.5. Sinkronisasi Dependencies + +Gunakan command `go mod tidy` untuk sinkronisasi dependencies yang digunakan dalam project. Dengan command tersebut, secara otomatis 3rd party yang belum ditambahkan akan dicatat dan ditambahkan; dan yang tidak digunakan tapi terlanjut tercatat akan dihapuskan. + +--- + + diff --git a/A-61-worker-pool.md b/A-61-worker-pool.md new file mode 100644 index 000000000..e69de29bb diff --git a/B-1-golang-web-hello-world.md b/B-1-golang-web-hello-world.md new file mode 100644 index 000000000..4f5a1b992 --- /dev/null +++ b/B-1-golang-web-hello-world.md @@ -0,0 +1,158 @@ +# B.1. Golang Web App: Hello World + +Pada serial bab B ini, kita masih tetap akan belajar tentang topik fundamental atau dasar tapi lebih spesifik ke arah web development. Kita awali dengan pembahasan bagaimana cara membuat aplikasi web "Hello World" sederhana menggunakan Go. + +## B.1.1. Pembuatan Aplikasi + +Mari belajar dengan praktek langsung. Pertama buat folder projek baru dengan isi `main.go`, tentukan package-nya sebagai `main`, lalu import package `fmt` dan `net/http`. + +```go +package main + +import "fmt" +import "net/http" +``` + +Setelah itu, siapkan dua buah fungsi, masing-masing fungsi memiliki skema parameter yang sama seperti berikut. + + - Parameter ke-1 bertipe `http.ResponseWrite` + - Parameter ke-2 bertipe `*http.Request` + +Fungsi dengan struktur di atas diperlukan oleh `http.HandleFunc` untuk keperluan penanganan request ke rute yang ditentukan. Berikut merupakan dua fungsi yang dimaksud. + +```go +func handlerIndex(w http.ResponseWriter, r *http.Request) { + var message = "Welcome" + w.Write([]byte(message)) +} + +func handlerHello(w http.ResponseWriter, r *http.Request) { + var message = "Hello world!" + w.Write([]byte(message)) +} +``` + +Method `Write()` milik parameter pertama (yang bertipe `http.ResponseWrite`), digunakan untuk meng-output-kan nilai balik data. Argumen method adalah data yang ingin dijadikan output, ditulis dalam bentuk `[]byte`. + +Pada contoh ini, data yang akan kita tampilkan bertipe string, maka perlu dilakukan casting dari `string` ke `[]byte`. Contohnya bisa dilihat seperta pada kode di atas, di bagian `w.Write([]byte(message))`. + +Selanjutnya, siapkan fungsi `main()` dengan isi di dalamnya adalah beberapa rute atau route, dengan aksi adalah kedua fungsi yang sudah disiapkan di atas. Tak lupa siapkan juga kode untuk start server. + +```go +func main() { + http.HandleFunc("/", handlerIndex) + http.HandleFunc("/index", handlerIndex) + http.HandleFunc("/hello", handlerHello) + + var address = "localhost:9000" + fmt.Printf("server started at %s\n", address) + err := http.ListenAndServe(address, nil) + if err != nil { + fmt.Println(err.Error()) + } +} +``` + +Fungsi `http.HandleFunc()` digunakan untuk routing. Parameter pertama adalah rute dan parameter ke-2 adalah handler-nya. + +Fungsi `http.ListenAndServe()` digunakan membuat sekaligus start server baru, dengan parameter pertama adalah alamat web server yang diiginkan (bisa diisi host, host & port, atau port saja). Parameter kedua merupakan object mux atau multiplexer. + +> Dalam chapter ini kita menggunakan default mux yang sudah disediakan oleh Go, jadi untuk parameter ke-2 cukup isi dengan `nil`. + +Ok, sekarang program sudah siap, jalankan menggunakan `go run`. + +![Jalankan program](images/B.1_1_start_server.png) + +Cek pada browser rute yang sudah dibuat, output akan muncul. + +![Mengakses aplikasi web](images/B.1_2_browse.png) + +Berikut merupakan penjelasan detail per-bagian program yang telah kita buat dari contoh di atas. + +### B.1.1.1. Penggunaan `http.HandleFunc()` + +Fungsi ini digunakan untuk **routing**, menentukan aksi dari sebuah url tertentu ketika diakses (di sini url tersebut kita sebut sebagai rute/route). Rute dituliskan dalam `string` sebagai parameter pertama, dan aksi-nya sendiri dibungkus dalam fungsi (bisa berupa closure) yang ditempatkan pada parameter kedua (kita sebut sebagai handler). + +Pada kode di atas, tiga buah rute didaftarkan: + + - Rute `/` dengan aksi adalah fungsi `handlerIndex()` + - Rute `/index` dengan aksi adalah sama dengan `/`, yaitu fungsi `handlerIndex()` + - Rute `/hello` dengan aksi fungsi `handlerHello()` + +Ketika rute-rute tersebut diakses lewat browser, outpunya adalah isi-handler dari rute yang bersangkutan. Kebetulan pada bab ini, ketiga rute tersebut outputnya adalah sama, yaitu berupa string. + +> Pada contoh di atas, ketika rute yang tidak terdaftar diakses, secara otomatis handler rute `/` akan terpanggil. + +### B.1.1.2. Penjelasan Mengenai **Handler** + +Route handler atau handler atau parameter kedua fungsi `http.HandleFunc()`, adalah sebuah fungsi dengan ber-skema `func (ResponseWriter, *Request)`. + + - Parameter ke-1 merupakan objek untuk keperluan http response. + - Sedang parameter ke-2 yang bertipe `*request` ini, berisikan informasi-informasi yang berhubungan dengan http request untuk rute yang bersangkutan. + +Contoh penulisan handler bisa dilihat pada fungsi `handlerIndex()` berikut. + +```go +func handlerIndex(w http.ResponseWriter, r *http.Request) { + var message = "Welcome" + w.Write([]byte(message)) +} +``` + +Output dari rute, dituliskan di dalam handler menggunakan method `Write()` milik objek `ResponseWriter` (parameter pertama). Output bisa berupa apapun, untuk output text tinggal lakukan casting dari tipe `string` ke `[]byte`, aturan ini juga berlaku untuk beberapa jenis output lainnya seperti html dan json, namun response header `Content-Type` perlu disesuaikan. + +Pada contoh program yang telah kita buat, handler `Index()` memunculkan text `"Welcome"`, dan handler `Hello()` memunculkan text `"Hello world!"`. + +Sebuah handler bisa dipergunakan pada banyak rute, bisa dilihat pada di atas handler `Index()` digunakan pada rute `/` dan `/index`. + +### B.1.1.3. Penggunaan `http.ListenAndServe()` + +Fungsi ini digunakan untuk membuat web server baru. Pada contoh yang telah dibuat, web server di-*start* pada port `9000` (bisa dituliskan dalam bentuk `localhost:9000` atau cukup `:9000` saja). + +```go +var address = ":9000" +fmt.Printf("server started at %s\n", address) +err := http.ListenAndServe(address, nil) +``` + +Fungsi `http.ListenAndServe()` bersifat blocking, menjadikan semua statement setelahnya tidak akan dieksekusi, sebelum di-stop. + +Fungsi ini mengembalikan nilai balik ber-tipe `error`. Jika proses pembuatan web server baru gagal, maka kita bisa mengetahui root-cause nya apa. + +## B.1.2. Web Server Menggunakan `http.Server` + +Selain menggunakan `http.ListenAndServe()`, ada cara lain yang bisa diterapkan untuk start web server, yaitu dengan memanfaatkan struct `http.Server`. + +Kode di bagian start server yang sudah kita buat, jika diubah ke cara ini, kurang lebih menjadi seperti berikut. + +```go +var address = ":9000" +fmt.Printf("server started at %s\n", address) + +server := new(http.Server) +server.Addr = address +err := server.ListenAndServe() +if err != nil { + fmt.Println(err.Error()) +} +``` + +Informasi host/port perlu dimasukan dalam property `.Addr` milik objek server. Lalu dari objek tersebut panggil method `.ListenAndServe()` untuk start web server. + +Kelebihan menggunakan `http.Server` salah satunya adalah kemampuan untuk mengubah beberapa konfigurasi default web server Go. + +Contoh, pada kode berikut, timeout untuk read request dan write request di ubah menjadi 10 detik. + +``` +server.ReadTimeout = time.Second * 10 +server.WriteTimeout = time.Second * 10 +``` + +Ada banyak lagi property dari struct `http.Server` ini, yang pastinya akan dibahas pada bab-bab selanjutnya. + +--- + + diff --git a/B-10-render-html-string.md b/B-10-render-html-string.md new file mode 100644 index 000000000..8190f3eaf --- /dev/null +++ b/B-10-render-html-string.md @@ -0,0 +1,63 @@ +# B.10. Template: Render HTML String + +Output HTML yang muncul, selain bersumber dari template view, bisa juga bersumber dari sebuah string. Dengan menggunakan method `Parse()` milik `*template.Template` kita bisa menjadikan string html sebagai output. + +## B.10.1. Praktek + +Langsung saja kita praktekkan, siapkan folder projek baru beserta file `main.go`, isi dengan kode berikut. + +```go +package main + +import "net/http" +import "fmt" +import "html/template" + +const view string = ` + + Template + + +

Hello

+ +` +``` + +Konstanta bernama `view` bertipe `string` disiapkan, dengan isi adalah string html yang akan kita jadikan sebagai output nantinya. + +Kemudian buat fungsi `main()`, isinya adalah route handler `/index`. Dalam handler tersebut, string html `view` diparsing lalu dirender sebagai output. + +Tambahkan juga rute `/`, yang isinya adalah me-redirect request secara paksa ke `/index` menggunakan fungsi `http.Redirect()`. + +```go +func main() { + http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) { + var tmpl = template.Must(template.New("main-template").Parse(view)) + if err := tmpl.Execute(w, nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/index", http.StatusTemporaryRedirect) + }) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Pada kode di atas bisa dilihat, sebuah template bernama `main-template` disiapkan. Template tersebut diisi dengan hasil parsing string html `view` lewat method `Parse()`. + +## B.10.2. Test + +Lakukan tes dan lihat hasilnya. + +![String html sebagai output](images/B.10_1_parse.png) + +--- + + diff --git a/B-11-http-method.md b/B-11-http-method.md new file mode 100644 index 000000000..12ac3053f --- /dev/null +++ b/B-11-http-method.md @@ -0,0 +1,59 @@ +# B.11. HTTP Method: POST & GET + +Setelah sebelumnya kita telah mempelajari banyak hal yang berhubungan dengan template view, kali ini topik yang terpilih adalah berbeda, yaitu mengenai penanganan http request di back end. + +Sebuah route handler pada dasarnya bisa menerima segala jenis request, dalam artian: apapun jenis HTTP method-nya maka akan tetap masuk ke satu handler (seperti **POST**, **GET**, dan atau lainnya). Untuk memisah request berdasarkan method-nya, bisa menggunakan seleksi kondisi. + +> Pada bab lain kita akan belajar teknik routing yg lebih advance dengan bantuan routing library. + +## B.11.1. Praktek + +Silakan pelajari dan praktekkan kode berikut. + +```go +package main + +import "net/http" +import "fmt" + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + w.Write([]byte("post")) + case "GET": + w.Write([]byte("get")) + default: + http.Error(w, "", http.StatusBadRequest) + } + }) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Struct `*http.Request` memiliki property bernama `Method` yang bisa digunakan untuk mengecek method daripada request yang sedang berjalan. + +Pada contoh di atas, request ke rute `/` dengan method POST akan menghasilkan output text `post`, sedangkan method GET menghasilkan output text `get`. + +## B.11.2. Test + +Gunakan [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en), atau tools sejenisnya untuk mempermudah testing. Berikut adalah contoh request dengan method GET. + +![Request GET](images/B.11_1_get.png) + +Sedangkan di bawah ini adalah untuk method POST. + +![Request POST](images/B.11_2_post.png) + +Jika method yang digunakan adalah selain POST dan GET, maka sesuai source code di atas, harusnya request akan menghasilkan response **400 Bad Request**. Di bawah ini adalah contoh request dengan method **PUT**. + +![400 Bad Request](images/B.11_3_bad_request.png) + +--- + + diff --git a/B-12-form-value.md b/B-12-form-value.md new file mode 100644 index 000000000..693a51513 --- /dev/null +++ b/B-12-form-value.md @@ -0,0 +1,137 @@ +# B.12. Form Value + +Pada bab ini kita akan belajar bagaimana cara untuk submit data, dari form di layer front end, ke back end. + +# B.12.1. Front End + +Pertama siapkan folder projek baru, dan sebuah file template view `view.html`. Pada file ini perlu didefinisikan 2 buah template, yaitu `form` dan `result`. Template pertama (`form`) dijadikan landing page program, isinya beberapa inputan untuk submit data. + +```html +{{define "form"}} + + + + Input Message + + +
+ + +
+ + + +
+ + +
+ + +{{end}} +``` + +Aksi dari form di atas adalah `/process`, yang dimana url tersebut nantinya akan mengembalikan output berupa html hasil render template `result`. Silakan tulis template result berikut dalam `view.html` (jadi file view ini berisi 2 buah template). + +```html +{{define "result"}} + + + + Show Message + + +

Hello {{.name}}

+

{{.message}}

+ + +{{end}} +``` + +## B.12.2. Back End + +Buat file `main.go`. Dalam file ini 2 buah route handler diregistrasikan. + + - Route `/` adalah landing page, menampilkan form input. + - Route `/process` sebagai action dari form input, menampilkan text. + +```go +package main + +import "net/http" +import "fmt" +import "html/template" + +func main() { + http.HandleFunc("/", routeIndexGet) + http.HandleFunc("/process", routeSubmitPost) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Handler route `/` dibungkus dalam fungsi bernama `routeIndexGet`. Di dalamnya, template `form` dalam file template `view.html` akan di-render ke view. Request dalam handler ini hanya dibatasi untuk method GET saja, request dengan method lain akan menghasilkan response 400 Bad Request. + +```go +func routeIndexGet(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + var tmpl = template.Must(template.New("form").ParseFiles("view.html")) + var err = tmpl.Execute(w, nil) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } + + http.Error(w, "", http.StatusBadRequest) +} +``` + +Fungsi `routeSubmitPost` yang merupakan handler route `/process`, berisikan proses yang mirip seperti handler route `/`, yaitu parsing `view.html` untuk di ambil template `result`-nya. Selain itu, pada handler ini ada proses pengambilan data yang dikirim dari form ketika di-submit, untuk kemudian disisipkan ke template view. + +```go +func routeSubmitPost(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + var tmpl = template.Must(template.New("result").ParseFiles("view.html")) + + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var name = r.FormValue("name") + var message = r.Form.Get("message") + + var data = map[string]string{"name": name, "message": message} + + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } + + http.Error(w, "", http.StatusBadRequest) +} +``` + +Ketika user submit ke `/process`, maka data-data yang ada di form input dikirim. Method `ParseForm()` pada statement `r.ParseForm()` berguna untuk parsing form data yang dikirim dari view, sebelum akhirnya bisa diambil data-datanya. Method tersebut mengembalikan data `error` jika proses parsing gagal (kemungkinan karena data yang dikirim ada yang tidak valid). + +Pengambilan data yang dikirim dilakukan lewat method `FormValue()`. Contohnya seperti pada kode di atas, `r.FormValue("name")`, akan mengembalikan data inputan `name` (data dari inputan ``). + +Selain lewat method `FormValue()`, pengaksesan data juga bisa dilakukan dengan cara mengakses property `Form` terlebih dahulu, kemudian mengakses method `Get()`. Contohnya seperti `r.Form.Get("message")`, yang akan menghasilkan data inputan `message`. Hasil dari kedua cara di atas adalah sama. + +Setelah data dari form sudah ditangkap oleh back-end, data ditampung dalam variabel `data` yang bertipe `map[string]string`. Variabel `data` tersebut kemudian disisipkan ke view, lewat statement `tmpl.Execute(w, data)`. + +## B.12.3. Test + +OK, sekarang coba jalankan program yang telah kita buat, dan cek hasilnya. + +![Form Value](images/B.12_1_form.png) + +--- + + diff --git a/B-13-form-upload-file.md b/B-13-form-upload-file.md new file mode 100644 index 000000000..3f5eb5d85 --- /dev/null +++ b/B-13-form-upload-file.md @@ -0,0 +1,177 @@ +# B.13. Form Upload File + +Pada bagian ini kita akan belajar bagaimana cara meng-handle upload file lewat form. Di beberapa bagian caranya mirip seperti pada bab sebelumnya, hanya perlu ditambahkan proses untuk handling file yang di-upload. File tersebut disimpan ke dalam path/folder tertentu. + +## B.13.1. Struktur Folder Proyek + +Sebelum mulai masuk ke bagian koding, siapkan terlebih dahulu file dan folder dengan struktur seperti gambar berikut. + +![Folder Structure](images/B.13_1_structure.png) + +Program sederhana yang akan kita buat, memiliki satu form dengan 2 inputan, alias dan file. Data file nantinya disimpan pada folder `files` yang telah dibuat, dengan nama sesuai nama file aslinya. Kecuali ketika user mengisi inputan alias, maka nama tersebut yang akan digunakan sebagai nama file tersimpan. + +## B.13.2. Front End + +Di bagian front end, isi file `view.html` dengan kode berikut. Template file ini nantinya yang dimunculkan sebagai landing page. + +```html + + + + Input Message + + +
+ +
+ + +
+ + +
+ + +``` + +Perlu diperhatikan, pada tag `
` perlu ditambahkan atribut `enctype="multipart/form-data"`, agar http request mendukung upload file. + +## B.13.3. Back End + +Di layer back end ada cukup banyak package yang perlu di-import, seperti `os, io, path/filepath`, dan lainnya. Packages tersebut kita perlukan untuk handling file upload. + +Pada fungsi `main()` siapkan 2 buah route handler, satu untuk landing page, dan satunya lagi digunakan ketika proses upload selesai (sama seperti pada bab sebelumnya). + +```go +package main + +import "net/http" +import "fmt" +import "os" +import "io" +import "path/filepath" +import "html/template" + +func main() { + http.HandleFunc("/", routeIndexGet) + http.HandleFunc("/process", routeSubmitPost) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Handler route `/` isinya proses untuk menampilkan landing page (file `view.html`). Method yang diperbolehkan mengakses rute ini hanya `GET`. + +```go +func routeIndexGet(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + http.Error(w, "", http.StatusBadRequest) + return + } + + var tmpl = template.Must(template.ParseFiles("view.html")) + var err = tmpl.Execute(w, nil) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} +``` + +Selanjutnya siapkan handler untuk rute `/proccess`, yaitu fungsi `routeSubmitPost`. Gunakan statement `r.ParseMultipartForm(1024)` untuk parsing form data yang dikirim. + +```go +func routeSubmitPost(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "", http.StatusBadRequest) + return + } + + if err := r.ParseMultipartForm(1024); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // ... +} +``` + +Method `ParseMultipartForm()` digunakan untuk mem-parsing form data yang ada data file nya. Argumen `1024` pada method tersebut adalah `maxMemory`. Pemanggilan method tersebut membuat file yang terupload disimpan sementara pada memory dengan alokasi adalah sesuai dengan `maxMemory`. Jika ternyata kapasitas yang sudah dialokasikan tersebut tidak cukup, maka file akan disimpan dalam temporary file. + +Masih dalam fungsi `routeSubmitPost()`, tambahkan kode untuk mengambil data alias dan file. + +```go +alias := r.FormValue("alias") + +uploadedFile, handler, err := r.FormFile("file") +if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return +} +defer uploadedFile.Close() + +dir, err := os.Getwd() +if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return +} +``` + +Statement `r.FormFile("file")` digunakan untuk mengambil file yg di upload, mengembalikan 3 objek: + + - Objek bertipe multipart.File (yang merupakan turunan dari `*os.File`) + - Informasi header file (bertipe `*multipart.FileHeader`) + - Dan `error` jika ada + +Tahap selanjutnya adalah, menambahkan kode membuat file baru, yang nantinya file ini akan diisi dengan isi dari file yang ter-upload. Jika inputan `alias` di-isi, maka nama nilai inputan tersebut dijadikan sebagai nama file. + +```go +filename := handler.Filename +if alias != "" { + filename = fmt.Sprintf("%s%s", alias, filepath.Ext(handler.Filename)) +} + +fileLocation := filepath.Join(dir, "files", filename) +targetFile, err := os.OpenFile(fileLocation, os.O_WRONLY|os.O_CREATE, 0666) +if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return +} +defer targetFile.Close() + +if _, err := io.Copy(targetFile, uploadedFile); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return +} + +w.Write([]byte("done")) +``` + +Fungsi `filepath.Ext` digunakan untuk mengambil ekstensi dari sebuah file. Pada kode di atas, `handler.Filename` yang berisi nama file terupload diambil ekstensinya, lalu digabung dengan `alias` yang sudah terisi. + +Fungsi `filepath.Join` berguna untuk pembentukan path. + +Fungsi `os.OpenFile` digunakan untuk membuka file. Fungsi ini membutuhkan 3 buah parameter: + + - Parameter pertama merupakan path atau lokasi dari file yang ingin di buka + - Parameter kedua adalah flag mode, apakah *read only*, *write only*, atau keduanya, atau lainnya. + - `os.O_WRONLY|os.O_CREATE` maknanya, file yang dibuka hanya akan bisa di tulis saja (*write only* konsantanya adalah `os.O_WRONLY`), dan file tersebut akan dibuat jika belum ada (konstantanya `os.O_CREATE`). + - Sedangkan parameter terakhir adalah permission dari file, yang digunakan dalam pembuatan file itu sendiri. + +Fungsi `io.Copy` akan mengisi konten file parameter pertama (`targetFile`) dengan isi parameter kedua (`uploadedFile`). File kosong yang telah kita buat tadi akan diisi dengan data file yang tersimpan di memory. + +> Nantinya pada salah satu pembahasan di [Bab B.16. AJAX Multiple File Upload](/B-16-ajax-multi-upload.html) akan dijelaskan cara handling file upload dengan metode yang lebih efektif dan hemat memori, yaitu menggunakan `MultipartReader`. + +## B.13.4. Testing + +Jalankan program, test hasilnya lewat browser. + +![Test upload](images/B.13_2_files.png) + +--- + + diff --git a/B-14-ajax-json-payload.md b/B-14-ajax-json-payload.md new file mode 100644 index 000000000..27cc4b244 --- /dev/null +++ b/B-14-ajax-json-payload.md @@ -0,0 +1,197 @@ +# B.14. AJAX JSON Payload + +Sebelumnya kita telah belajar bagaimana cara submit data dari front-end ke back-end menggunakan teknik **Form Data**. Kali ini kita akan belajar tentang cara request menggunakan teknik **Request Payload** dengan tipe payload adalah **JSON**. + +Teknik request **Form Data** digunakan salah satu nya pada request submit lewat ``. Pada bab ini, kita tidak akan menggunakan cara submit lewat form, melainkan menggunakan teknik AJAX (Asynchronous JavaScript And XML), dengan payload ber-tipe JSON. + +[Perbedaan](http://stackoverflow.com/a/23152367/1467988) antara kedua jenis request tersebut adalah pada isi header `Content-Type`, dan bentuk informasi dikirimkan. Secara default, request lewat ``, content type-nya adalah `application/x-www-form-urlencoded`. Data dikirimkan dalam bentuk query string (key-value) seperti `id=n001&nama=bruce`. + +> Ketika di form ditambahkan atribut `enctype="multipart/form-data"`, maka content type berubah menjadi `multipart/form-data`. + +Request Payload JSON sedikit berbeda, `Content-Type` berisikan `application/json`, dan data disisipkan dalam `Body` dalam bentuk **JSON** string. + +## B.14.1. Struktur Folder Proyek + +OK, langsung saja, pertama siapkan proyek dengan struktur seperti pada gambar di bawah ini. + +![Struktur proyek](images/B.14_1_structure.png) + +> Silakan unduh file js jQuery dari situs official-nya. + +## B.14.2. Front End - HTML + +Layout dari view perlu disiapkan terlebih dahulu, tulis kode berikut pada file `view.html`. + +```html + + + + JSON Payload + + + + +

+ + +
+ + +``` + +Selanjutnya, pada tag `
` tambahkan tabel sederhana berisikan inputan-inputan yang diperlukan. Ada tiga buah inputan yang harus dipersiapkan, yaitu: *Name*, *Age*, dan *Gender*; dan juga sebuah button untuk submit form. + +```html + + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+``` + +## B.14.3. Front End - HTML + +Sekarang kita masuk ke bagian paling menyenangkan/menyebalkan (tergantung taste), yaitu javascript. Siapkan sebuah event `submit` pada `#user-form`. Dalam event tersebut default handler event submit milik `` di-override, diganti dengan AJAX request. + +```js +$("#user-form").on("submit", function (e) { + e.preventDefault(); + + var $self = $(this); + var payload = JSON.stringify({ + name: $('[name="name"]').val(), + age: parseInt($('[name="age"]').val(), 10), + gender: $('[name="gender"]').val() + }); + + $.ajax({ + url: $self.attr("action"), + type: $self.attr("method"), + data: payload, + contentType: 'application/json', + }).then(function (res) { + $(".message").text(res); + }).catch(function (a) { + alert("ERROR: " + a.responseText); + }); +}); +``` + +Value semua inputan diambil lalu dimasukkan dalam sebuah objek lalu di stringify (agar menjadi JSON string), untuk kemudian di jadikan sebagai payload request. Bisa dilihat pada kode AJAX di atas, `contentType` nilainya adalah `application/json`. + +Respon dari ajax di atas akan dimunculkan pada `

`. + +## B.14.4. Back End + +3 buah rute perlu disiapkan, yang pertama adalah untuk menampilkan `view.html`, untuk keperluan submit data, dan registrasi asset. + +```go +package main + +import "fmt" +import "net/http" +import "html/template" +import "encoding/json" + +func main() { + http.HandleFunc("/", handleIndex) + http.HandleFunc("/save", handleSave) + + http.Handle("/static/", + http.StripPrefix("/static/", + http.FileServer(http.Dir("assets")))) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Handler `handleIndex` berisikan kode untuk parsing `view.html`. + +```go +func handleIndex(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("view.html")) + if err := tmpl.Execute(w, nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} +``` + +Sedangkan `handleSave` akan memproses request yang di-submit dari bagian depan. + +```go +func handleSave(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + decoder := json.NewDecoder(r.Body) + payload := struct { + Name string `json:"name"` + Age int `json:"age"` + Gender string `json:"gender"` + }{} + if err := decoder.Decode(&payload); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + message := fmt.Sprintf( + "hello, my name is %s. I'm %d year old %s", + payload.Name, + payload.Age, + payload.Gender, + ) + w.Write([]byte(message)) + return + } + + http.Error(w, "Only accept POST request", http.StatusBadRequest) +} +``` + +Isi payload didapatkan dengan cara men-decode body request (`r.Body`). Proses decoding tidak dilakukan menggunakan `json.Unmarshal()` melainkan lewat json decoder, karena akan [lebih efisien](http://stackoverflow.com/a/21198571/1467988) untuk jenis kasus seperti ini. + +> Gunakan `json.Decoder` jika data adalah stream `io.Reader`. Gunakan `json.Unmarshal()` untuk decode data sumbernya sudah ada di memory. + +## B.14.5. Test + +Jalankan program, test hasilnya di browser. + +![Hasil tes](images/B.14_2_test.png) + +Gunakan fasilitas Developer Tools pada Chrome untuk melihat detail dari request. + +![Request](images/B.14_3_inspect.png) + +--- + + diff --git a/B-15-ajax-json-response.md b/B-15-ajax-json-response.md new file mode 100644 index 000000000..5c874834f --- /dev/null +++ b/B-15-ajax-json-response.md @@ -0,0 +1,90 @@ +# B.15. AJAX JSON Response + +Pada bab sebelumnya, kita belajar cara untuk memproses request dengan paylaod bertipe JSON. Pada bab ini kita akan belajar untuk membuat satu endpoint yang mengembalikan data JSON string. + +## B.15.1. Praktek + +Siapkan satu buah folder proyek baru, dengan satu buah file di dalamnya bernama `main.go`. Dalam file ini siapkan rute `/`. + +```go +package main + +import "fmt" +import "net/http" +import "encoding/json" + +func main() { + http.HandleFunc("/", ActionIndex) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Selanjutnya buat handler untuk rute `/`. Di dalam fungsi ini, data dummy ber-tipe slice object disiapkan. Data ini akan dikonversi ke JSON lalu dijadikan nilai balik endpoint `/`. + +```go +func ActionIndex(w http.ResponseWriter, r *http.Request) { + data := [] struct { + Name string + Age int + } { + { "Richard Grayson", 24 }, + { "Jason Todd", 23 }, + { "Tim Drake", 22 }, + { "Damian Wayne", 21 }, + } + + jsonInBytes, err := json.Marshal(data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(jsonInBytes) +} +``` + +Cara mengkonversi data ke bentuk json cukup mudah, bisa menggunakan `json.Marshal()`. Fungsi ini mengembalikan dua nilai balik, data json (dalam bentuk `[]byte`) dan error jika ada. + +> Untuk mengambil bentuk string dari hasil konversi JSON, cukup lakukan casting pada data slice bytes tersebut. Contoh: `string(jsonInBytes)` + +Karena nilai balik konversi sudah dalam bentuk bytes, maka langsung saja panggil method `Write()` milik `http.ResponseWriter` dan sisipkan data json sebagai argument pemanggilan method. + +Jangan lupa juga untuk menambahkan response header `Content-Type: application/json`. + +## B.15.2. Testing + +OK, semua sudah selesai, lakukan testing. + +![Testing web server](images/B.15_1_test.png) + +## B.15.3. JSON Response menggunakan JSON.Encoder + +Di bab sebelumnya sudah disinggung, bahwa lebih baik menggunakan `json.Decoder` jika ingin men-decode data yang sumbernya ada di stream `io.Reader` + +Package json juga memiliki fungsi lain-nya yaitu `json.Encoder`, yang sangat cocok digunakan untuk meng-encode data menjadi JSON dengan tujuan objek langsung ke stream `io.Reader`. + +Karena tipe `http.ResponseWriter` adalah meng-embed `io.Reader`, maka jelasnya bisa kita terapkan penggunaan encoder di sini. + +Contohnya penerapannya sebagai berikut. + +```go +w.Header().Set("Content-Type", "application/json") + +err := json.NewEncoder(w).Encode(data) +if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return +} +``` + +Kode di atas hasilnya ekuivalen dengan penggunaan `json.Marshal`. + +--- + + diff --git a/B-16-ajax-multi-upload.md b/B-16-ajax-multi-upload.md new file mode 100644 index 000000000..40175762f --- /dev/null +++ b/B-16-ajax-multi-upload.md @@ -0,0 +1,205 @@ +# B.16. AJAX Multiple File Upload + +Pada bab ini, kita akan belajar 3 hal dalam satu waktu, yaitu: + +1. Bagaiamana cara untuk upload file via AJAX. +2. Cara untuk handle upload banyak file sekaligus. +3. Cara handle upload file yang lebih hemat memori. + +Sebelumnya pada [Bab B.13. Form Upload File](/B-13-form-upload-file.html), pemrosesan file upload dilakukan lewat **ParseMultipartForm**, sedangkan pada bab ini metode yang dipakai berbeda, yaitu menggunakan **MultipartReader**. + +Kelebihan dari `MultipartReader` adalah, file yang di upload **tidak** di simpan sebagai file temporary di lokal terlebih dahulu (tidak seperti `ParseMultipartForm`), melainkan langsung diambil dari stream `io.Reader`. + +Di bagian front end, upload file secara asynchronous bisa dilakukan menggunakan objek [FormData](https://developer.mozilla.org/en/docs/Web/API/FormData). Semua file dimasukkan dalam objek `FormData`, setelah itu objek tersebut dijadikan sebagai payload AJAX request. + +## B.16.1. Struktur Folder Proyek + +Mari langsung kita praktekkan, pertama siapkan proyek dengan struktur seperti gambar di bawah ini. + +![Folder Structure](images/B.16_1_structure.png) + +> Silakan unduh file js jQuery dari situs official jQuery. + +## B.16.2. Front End + +Buka `view.html`, siapkan template dasar view. Dalam file ini terdapat satu buah inputan upload file yang mendukung multi upload, dan satu buah tombol submit. + +Untuk meng-enable kapabilitas multi upload, cukup tambahkan atribut `multiple` pada input file. + +```html + + + + Multiple Upload + + + + + + +
+ +
+ + +``` + +Override event `submit` pada form `#user-form`, handler event ini berisikan proses mulai pembentukan objek `FormData` dari file-file yang telah di upload, hingga eksekusi AJAX. + +```js +$("#user-form").on("submit", function (e) { + e.preventDefault(); + + var $self = $(this); + var files = $("#upload-file")[0].files; + var formData = new FormData(); + + for (var i = 0; i < files.length; i++) { + formData.append("files", files[i]); + } + + $.ajax({ + url: $self.attr("action"), + type: $self.attr("method"), + data: formData, + processData: false, + contentType: false, + }).then(function (res) { + alert(res); + $("#user-form").trigger("reset"); + }).catch(function (a) { + alert("ERROR: " + a.responseText); + }); +}); +``` + +Objek inputan files (yang didapat dari `$("#upload-file")[0].files`) memiliki property `.files` yang isinya merupakan array dari semua file yang dipilih oleh user ketika upload. File-file tersebut di-loop, dimasukkan ke dalam objek `FormData` yang telah dibuat. + +AJAX dilakukan lewat `jQuery.ajax`. Berikut adalah penjelasan mengenai konfigurasi `processData` dan `contentType` dalam AJAX yang sudah dibuat. + + - Konfigurasi `contentType` perlu di set ke `false` agar header Content-Type yang dikirim bisa menyesuaikan data yang disisipkan. + - Konfigurasi `processData` juga perlu di set ke `false`, agar data yang akan di kirim tidak otomatis dikonversi ke query string atau json string (tergantung `contentType`). Pada konteks ini kita memerlukan payload tetap dalam tipe `FormData`. + +## B.16.3. Back End + +Ada 2 route handler yang harus dipersiapkan di back end. Pertama adalah rute `/` yang menampilkan form upload, dan rute `/upload` untuk pemrosesan upload sendiri. + +Buka file `main.go`, isi dengan package yang dibutuhkan, lalu lakukan registrasi dua rute yang dimaksud di atas, beserta satu buah rute untuk static assets. + +```go +package main + +import "fmt" +import "net/http" +import "html/template" +import "path/filepath" +import "io" +import "os" + +func main() { + http.HandleFunc("/", handleIndex) + http.HandleFunc("/upload", handleUpload) + http.Handle("/static/", + http.StripPrefix("/static/", + http.FileServer(http.Dir("assets")))) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Buat handler rute `/`, parsing template view `view.html`. + +```go +func handleIndex(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("view.html")) + if err := tmpl.Execute(w, nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} +``` + +Sebelumnya, pada [Bab B.13. Form Upload File](/B-13-form-upload-file.html), metode yang digunakan untuk handle file upload adalah menggunakan `ParseMultipartForm`, file diproses dalam memori dengan alokasi tertentu, dan jika melebihi alokasi maka akan disimpan pada temporary file. + +Metode tersebut kurang tepat guna jika digunakan untuk memproses file yang ukurannya besar (file size melebihi `maxMemory`) atau jumlah file-nya sangat banyak (memakan waktu, karena isi dari masing-masing file akan ditampung pada file *temporary* sebelum benar-benar di-copy ke file tujuan). + +Solusinya dari dua masalah di atas adalah menggunakan `MultipartReader` untuk handling file upload. Dengan metode ini, file destinasi isinya akan di-copy lagsung dari stream `io.Reader`, tanpa butuh file temporary untuk perantara. + +Kembali ke bagian perkodingan, siapkan fungsi `handleUpload`, isinya kode berikut. + +```go +func handleUpload(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Only accept POST request", http.StatusBadRequest) + return + } + + basePath, _ := os.Getwd() + reader, err := r.MultipartReader() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // ... +} +``` + +Bisa dilihat, method `.MultipartReader()` dipanggil dari objek request milik handler. Mengembalikan dua objek, pertama `*multipart.Reader` dan `error` (jika ada). + +Selanjutnya lakukan perulangan terhadap objek `reader`. Setiap file yang di-upload diproses di masing-masing perulangan. Setelah looping berakhir. idealnya semua file sudah terproses dengan benar. + +```go +for { + part, err := reader.NextPart() + if err == io.EOF { + break + } + + fileLocation := filepath.Join(basePath, "files", part.FileName()) + dst, err := os.Create(fileLocation) + if dst != nil { + defer dst.Close() + } + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if _, err := io.Copy(dst, part); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +w.Write([]byte(`all files uploaded`)) +``` + +Method `.NextPart()` mengembalikan 2 informasi, yaitu objek stream `io.Reader` (dari file yg di upload), dan `error`. + +File destinasi dipersiapkan, kemudian diisi dengan data dari stream file, menggunakan `io.Copy()`. + +Jika `reader.NextPart()` mengembalikan error `io.EOF`, menandakan bahwa semua file sudah di proses, maka hentikan perulangan. + +OK, semua persiapan sudah cukup. + +## B.16.4. Testing + +Buka browser, test program yang telah dibuat. Coba lakukan pengujian dengan beberapa buah file. + +![Upload files](images/B.16_2_upload_files.png) + +Cek apakah file sudah terupload. + +![Uploaded files](images/B.16_3_uploaded_files.png) + +--- + + diff --git a/B-17-download-file.md b/B-17-download-file.md new file mode 100644 index 000000000..4b2da24de --- /dev/null +++ b/B-17-download-file.md @@ -0,0 +1,225 @@ +# B.17. Download File + +Setelah sebelumnya belajar cara untuk handle upload file, kali ini kita akan belajar bagaimana cara membuat handler yang hasilnya adalah download file. + +Sebenarnya download file bisa dengan mudah di-implementasikan menggunakan teknik routing static file, langsung akses url dari public assets di browser. Namun outcome dari teknik ini sangat tergantung pada browser. Tiap browser memiliki behaviour berbeda, ada yang file tidak di-download melainkan dibuka di tab, ada yang ter-download. + +Dengan menggunakan teknik berikut, file pasti akan ter-download. + +## B.17.1. Struktur Folder Proyek + +OK, pertama siapkan terlebih dahulu proyek dengan struktur seperti gambar berikut. + +![Project structure](images/B.17_1_structure.png) + +File yang berada di folder `files` adalah dummy, jadi anda bisa gunakan file apapun dengan jumlah berapapun untuk keperluan belajar. + +## B.17.2. Front End + +Kali ini di bagian front end kita tidak menggunakan jQuery, cukup javascript saja tanpa library. + +Pertama siapkan dahulu template nya, isi file `view.html` dengan kode berikut. + +```html + + + + Download file + + + +
    + + +``` + +Tag `