WordPress Code Standards and Working $link Parameters In Shortcode

how to make this shortcode [parent-child] working with link=false , and no extract in shortcode.

You can never pass boolean value as shortcode parameter, rather it should be treated as string. Your comparison over the param link value false should be used as 'false' (string).

add_shortcode( 'parent-child', 'taxonomy_hierarchy' );
function taxonomy_hierarchy( $atts ){
    // Don't extract, rather parse shortcode params with defaults.
    $atts = shortcode_atts( array(
        'link' => 'true',
        'taxonomy' => 'property_city'
    ), $atts, 'parent-child' );

    // Check the $atts parameters and their typecasting.
    var_dump( $atts );

    global $post;
    $terms = wp_get_post_terms( $post->ID, $atts['taxonomy'] );

    /* You can pass conditions here to override
     * $link based on certain conditions. If it's
     * a single post, current user is editor, etc.
     */

    ob_start();
    foreach( $terms as $term ){
        if( $term->parent != 0 ){
            $parent_term = get_term( $term->parent, $taxonomy );
            if ($atts['link'] !== 'false') {
                printf( '<a href="%s">%s</a>, ', esc_url( get_term_link($parent_term) ), $parent_term->name );
            } else {
                echo $parent_term->name . ', ';
            }
        }

        if ($atts['link'] !== 'false') {
            printf( '<a href="%s">%s</a>', esc_url( get_term_link($term) ), $term->name );
        } else {
            echo $term->name;
        }
    }

    return ob_get_clean();
}

no echo in shortcode

It’s not about using echo, you are asked to not echo output within shortcode. Rather, you should return it as WordPress will replace shortcode with your output value. Here, you are buffering your output using ob_start() and ob_get_clean() functions and returning it. Which is just fine, and commonly used techniques.